remove .old_id entirely and make ABID generation only happen once on initial save
Some checks are pending
CodeQL / Analyze (python) (push) Waiting to run
Build Debian package / build (push) Waiting to run
Build Docker image / buildx (push) Waiting to run
Build Homebrew package / build (push) Waiting to run
Build GitHub Pages website / build (push) Waiting to run
Build GitHub Pages website / deploy (push) Blocked by required conditions
Run linters / lint (push) Waiting to run
Build Pip package / build (push) Waiting to run
Run tests / python_tests (ubuntu-22.04, 3.11) (push) Waiting to run
Run tests / docker_tests (push) Waiting to run

This commit is contained in:
Nick Sweeting 2024-09-04 16:40:15 -07:00
parent 22a6666bab
commit 68a39b7392
No known key found for this signature in database
11 changed files with 242 additions and 142 deletions

View file

@ -69,7 +69,6 @@ def get_abid_info(self, obj, request=None):
&nbsp; &nbsp; SUBTYPE: &nbsp; &nbsp; &nbsp; <code style="font-size: 10px;"><b style="user-select: all">{}</b> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {}</code> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <code style="font-size: 10px;"><b>{}</b></code> {}: <code style="user-select: all">{}</code><br/> &nbsp; &nbsp; SUBTYPE: &nbsp; &nbsp; &nbsp; <code style="font-size: 10px;"><b style="user-select: all">{}</b> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {}</code> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <code style="font-size: 10px;"><b>{}</b></code> {}: <code style="user-select: all">{}</code><br/>
&nbsp; &nbsp; RAND: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <code style="font-size: 10px;"><b style="user-select: all">{}</b> &nbsp; &nbsp; &nbsp; {}</code> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <code style="font-size: 10px;"><b>{}</b></code> {}: <code style="user-select: all">{}</code></code> &nbsp; &nbsp; RAND: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <code style="font-size: 10px;"><b style="user-select: all">{}</b> &nbsp; &nbsp; &nbsp; {}</code> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <code style="font-size: 10px;"><b>{}</b></code> {}: <code style="user-select: all">{}</code></code>
<br/><hr/> <br/><hr/>
&nbsp; &nbsp; <small style="opacity: 0.5">.old_id: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<code style="font-size: 10px; user-select: all">{}</code></small><br/>
</div> </div>
''', ''',
obj.api_url + (f'?api_key={get_or_create_api_token(request.user)}' if request and request.user else ''), obj.api_url, obj.api_docs_url, obj.api_url + (f'?api_key={get_or_create_api_token(request.user)}' if request and request.user else ''), obj.api_url, obj.api_docs_url,
@ -82,7 +81,6 @@ def get_abid_info(self, obj, request=None):
highlight_diff(obj.ABID.uri, derived_uri), highlight_diff(str(obj.ABID.uuid)[14:26], str(fresh_abid.uuid)[14:26]), mark_safe(uri_diff), obj.abid_uri_src, str(obj.ABID_FRESH_VALUES['uri']), highlight_diff(obj.ABID.uri, derived_uri), highlight_diff(str(obj.ABID.uuid)[14:26], str(fresh_abid.uuid)[14:26]), mark_safe(uri_diff), obj.abid_uri_src, str(obj.ABID_FRESH_VALUES['uri']),
highlight_diff(obj.ABID.subtype, derived_subtype), highlight_diff(str(obj.ABID.uuid)[26:28], str(fresh_abid.uuid)[26:28]), mark_safe(subtype_diff), obj.abid_subtype_src, str(obj.ABID_FRESH_VALUES['subtype']), highlight_diff(obj.ABID.subtype, derived_subtype), highlight_diff(str(obj.ABID.uuid)[26:28], str(fresh_abid.uuid)[26:28]), mark_safe(subtype_diff), obj.abid_subtype_src, str(obj.ABID_FRESH_VALUES['subtype']),
highlight_diff(obj.ABID.rand, derived_rand), highlight_diff(str(obj.ABID.uuid)[28:36], str(fresh_abid.uuid)[28:36]), mark_safe(rand_diff), obj.abid_rand_src, str(obj.ABID_FRESH_VALUES['rand'])[-7:], highlight_diff(obj.ABID.rand, derived_rand), highlight_diff(str(obj.ABID.uuid)[28:36], str(fresh_abid.uuid)[28:36]), mark_safe(rand_diff), obj.abid_rand_src, str(obj.ABID_FRESH_VALUES['rand'])[-7:],
highlight_diff(getattr(obj, 'old_id', ''), obj.pk),
) )
except Exception as e: except Exception as e:
return str(e) return str(e)

View file

@ -73,27 +73,30 @@ class ABIDModel(models.Model):
""" """
Abstract Base Model for other models to depend on. Provides ArchiveBox ID (ABID) interface. Abstract Base Model for other models to depend on. Provides ArchiveBox ID (ABID) interface.
""" """
abid_prefix: str = DEFAULT_ABID_PREFIX # e.g. 'tag_' abid_prefix: str = DEFAULT_ABID_PREFIX # e.g. 'tag_'
abid_ts_src = 'None' # e.g. 'self.created' abid_ts_src = 'self.created' # e.g. 'self.created'
abid_uri_src = 'None' # e.g. 'self.uri' abid_uri_src = 'None' # e.g. 'self.uri'
abid_subtype_src = 'None' # e.g. 'self.extractor' abid_subtype_src = 'self.__class__.__name__' # e.g. 'self.extractor'
abid_rand_src = 'None' # e.g. 'self.uuid' or 'self.id' abid_rand_src = 'self.id' # e.g. 'self.uuid' or 'self.id'
abid_salt: str = DEFAULT_ABID_URI_SALT abid_salt: str = DEFAULT_ABID_URI_SALT
# id = models.UUIDField(primary_key=True, default=uuid4, editable=True) # id = models.UUIDField(primary_key=True, default=None, null=False, editable=False, unique=True, verbose_name='ID')
# uuid = models.UUIDField(blank=True, null=True, editable=True, unique=True) # abid = ABIDField(prefix=abid_prefix)
abid = ABIDField(prefix=abid_prefix)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, default=get_or_create_system_user_pk) # created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, default=None, null=False)
created = AutoDateTimeField(default=None, null=False, db_index=True) # created = AutoDateTimeField(default=None, null=False, db_index=True)
modified = models.DateTimeField(auto_now=True) # modified = models.DateTimeField(auto_now=True)
class Meta(TypedModelMeta): class Meta(TypedModelMeta):
abstract = True abstract = True
def save(self, *args: Any, **kwargs: Any) -> None: def save(self, *args: Any, **kwargs: Any) -> None:
if self._state.adding: if self._state.adding:
self.issue_new_abid() self.pk = self.id = self.id or uuid4()
self.created = ts_from_abid(abid_part_from_ts(timezone.now())) # cut off precision to match precision of TS component
self.modified = self.created
self.created_by = self.created_by or get_or_create_system_user_pk()
self.abid = str(self.issue_new_abid())
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
# assert str(self.id) == str(self.ABID.uuid), f'self.id {self.id} does not match self.ABID {self.ABID.uuid}' # assert str(self.id) == str(self.ABID.uuid), f'self.id {self.id} does not match self.ABID {self.ABID.uuid}'
@ -119,48 +122,28 @@ class ABIDModel(models.Model):
def ABID_FRESH_HASHES(self) -> Dict[str, str]: def ABID_FRESH_HASHES(self) -> Dict[str, str]:
return abid_hashes_from_values(**self.ABID_FRESH_VALUES) return abid_hashes_from_values(**self.ABID_FRESH_VALUES)
def issue_new_abid(self):
assert self.abid is None, f'Can only issue new ABID for new objects that dont already have one {self.abid}'
assert self._state.adding, 'Can only issue new ABID when model._state.adding is True'
assert eval(self.abid_uri_src), f'Can only issue new ABID if self.abid_uri_src is defined ({self.abid_uri_src}={eval(self.abid_uri_src)})'
@property self.abid = None
def ABID_FRESH(self) -> ABID: self.pk = self.id = self.id or uuid4()
""" self.created = ts_from_abid(abid_part_from_ts(timezone.now())) # cut off precision to match precision of TS component
Return a pure freshly derived ABID (assembled from attrs defined in ABIDModel.abid_*_src).
"""
abid_fresh_values = self.ABID_FRESH_VALUES abid_fresh_values = self.ABID_FRESH_VALUES
assert all(abid_fresh_values.values()), f'All ABID_FRESH_VALUES must be set {abid_fresh_values}' assert all(abid_fresh_values.values()), f'All ABID_FRESH_VALUES must be set {abid_fresh_values}'
abid_fresh_hashes = self.ABID_FRESH_HASHES abid_fresh_hashes = self.ABID_FRESH_HASHES
assert all(abid_fresh_hashes.values()), f'All ABID_FRESH_HASHES must be able to be generated {abid_fresh_hashes}' assert all(abid_fresh_hashes.values()), f'All ABID_FRESH_HASHES must be able to be generated {abid_fresh_hashes}'
abid = ABID(**abid_fresh_hashes) new_abid = ABID(**abid_fresh_hashes)
assert abid.ulid and abid.uuid and abid.typeid, f'Failed to calculate {abid_fresh_values["prefix"]}_ABID for {self.__class__.__name__}' assert new_abid.ulid and new_abid.uuid and new_abid.typeid, f'Failed to calculate {abid_fresh_values["prefix"]}_ABID for {self.__class__.__name__}'
return abid
def issue_new_abid(self):
assert self.abid is None, f'Can only issue new ABID for new objects that dont already have one {self.abid}'
assert self._state.adding, 'Can only issue new ABID when model._state.adding is True'
assert eval(self.abid_uri_src), f'Can only issue new ABID if self.abid_uri_src is defined ({self.abid_uri_src}={eval(self.abid_uri_src)})'
self.old_id = getattr(self, 'old_id', None) or self.id or uuid4()
self.abid = None
self.created = ts_from_abid(abid_part_from_ts(getattr(self, 'bookmarked', None) or timezone.now())) # cut off precision to match precision of TS component
self.added = getattr(self, 'added', None) or self.created
self.modified = self.created
assert all(self.ABID_FRESH_VALUES.values()), f'Can only issue new ABID if all self.ABID_FRESH_VALUES are defined {self.ABID_FRESH_VALUES}'
new_abid = self.ABID_FRESH
# store stable ABID on local fields, overwrite them because we are adding a new entry and existing defaults havent touched db yet # store stable ABID on local fields, overwrite them because we are adding a new entry and existing defaults havent touched db yet
self.abid = str(new_abid) self.abid = str(new_abid)
self.id = new_abid.uuid assert str(self.ABID.uuid) == str(new_abid.uuid)
self.pk = new_abid.uuid return new_abid
assert self.ABID == new_abid
assert str(self.ABID.uuid) == str(self.id) == str(self.pk) == str(ABID.parse(self.abid).uuid)
self._ready_to_save_as_new = True
@property @property
@ -169,27 +152,12 @@ class ABIDModel(models.Model):
aka get_or_generate_abid -> ULIDParts(timestamp='01HX9FPYTR', url='E4A5CCD9', subtype='00', randomness='ZYEBQE') aka get_or_generate_abid -> ULIDParts(timestamp='01HX9FPYTR', url='E4A5CCD9', subtype='00', randomness='ZYEBQE')
""" """
# otherwise DB is single source of truth, load ABID from existing db pk if not self.abid:
abid: ABID | None = None pre_save_abid = self.issue_new_abid()
try: self.abid = str(pre_save_abid)
abid = abid or ABID.parse(cast(str, self.abid)) return pre_save_abid
except Exception:
pass
try:
abid = abid or ABID.parse(cast(str, self.id))
except Exception:
pass
try:
abid = abid or ABID.parse(cast(str, self.pk))
except Exception:
pass
abid = abid or self.ABID_FRESH
return abid
return ABID.parse(cast(str, self.abid))
@property @property
def ULID(self) -> ULID: def ULID(self) -> ULID:

View file

@ -0,0 +1,47 @@
# Generated by Django 5.1 on 2024-09-04 23:32
import abid_utils.models
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0007_alter_apitoken_created_by'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterField(
model_name='apitoken',
name='created',
field=abid_utils.models.AutoDateTimeField(db_index=True, default=None),
),
migrations.AlterField(
model_name='apitoken',
name='created_by',
field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='apitoken',
name='id',
field=models.UUIDField(default=None, editable=False, primary_key=True, serialize=False, unique=True, verbose_name='ID'),
),
migrations.AlterField(
model_name='outboundwebhook',
name='created',
field=abid_utils.models.AutoDateTimeField(db_index=True, default=None),
),
migrations.AlterField(
model_name='outboundwebhook',
name='created_by',
field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='outboundwebhook',
name='id',
field=models.UUIDField(default=None, editable=False, primary_key=True, serialize=False, unique=True, verbose_name='ID'),
),
]

View file

@ -12,7 +12,7 @@ from signal_webhooks.models import WebhookBase
from django_stubs_ext.db.models import TypedModelMeta from django_stubs_ext.db.models import TypedModelMeta
from abid_utils.models import ABIDModel, ABIDField, get_or_create_system_user_pk from abid_utils.models import ABIDModel, ABIDField, AutoDateTimeField
@ -32,11 +32,12 @@ class APIToken(ABIDModel):
abid_subtype_src = 'self.created_by_id' abid_subtype_src = 'self.created_by_id'
abid_rand_src = 'self.id' abid_rand_src = 'self.id'
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) id = models.UUIDField(primary_key=True, default=None, null=False, editable=False, unique=True, verbose_name='ID')
abid = ABIDField(prefix=abid_prefix) abid = ABIDField(prefix=abid_prefix)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, default=get_or_create_system_user_pk) created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, default=None, null=False)
created = models.DateTimeField(auto_now_add=True) created = AutoDateTimeField(default=None, null=False, db_index=True)
modified = models.DateTimeField(auto_now=True)
token = models.CharField(max_length=32, default=generate_secret_token, unique=True) token = models.CharField(max_length=32, default=generate_secret_token, unique=True)
expires = models.DateTimeField(null=True, blank=True) expires = models.DateTimeField(null=True, blank=True)
@ -99,11 +100,11 @@ class OutboundWebhook(ABIDModel, WebhookBase):
abid_subtype_src = 'self.ref' abid_subtype_src = 'self.ref'
abid_rand_src = 'self.id' abid_rand_src = 'self.id'
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) id = models.UUIDField(primary_key=True, default=None, null=False, editable=False, unique=True, verbose_name='ID')
abid = ABIDField(prefix=abid_prefix) abid = ABIDField(prefix=abid_prefix)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, default=get_or_create_system_user_pk) created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, default=None, null=False)
created = models.DateTimeField(auto_now_add=True) created = AutoDateTimeField(default=None, null=False, db_index=True)
modified = models.DateTimeField(auto_now=True) modified = models.DateTimeField(auto_now=True)
# More fields here: WebhookBase... # More fields here: WebhookBase...

View file

@ -64,7 +64,6 @@ class ArchiveResultSchema(Schema):
TYPE: str = 'core.models.ArchiveResult' TYPE: str = 'core.models.ArchiveResult'
id: UUID id: UUID
old_id: int
abid: str abid: str
modified: datetime modified: datetime
@ -127,9 +126,9 @@ class ArchiveResultSchema(Schema):
class ArchiveResultFilterSchema(FilterSchema): class ArchiveResultFilterSchema(FilterSchema):
id: Optional[str] = Field(None, q=['id__startswith', 'abid__icontains', 'old_id__startswith', 'snapshot__id__startswith', 'snapshot__abid__icontains', 'snapshot__timestamp__startswith']) id: Optional[str] = Field(None, q=['id__startswith', 'abid__icontains', 'snapshot__id__startswith', 'snapshot__abid__icontains', 'snapshot__timestamp__startswith'])
search: Optional[str] = Field(None, q=['snapshot__url__icontains', 'snapshot__title__icontains', 'snapshot__tags__name__icontains', 'extractor', 'output__icontains', 'id__startswith', 'abid__icontains', 'old_id__startswith', 'snapshot__id__startswith', 'snapshot__abid__icontains', 'snapshot__timestamp__startswith']) search: Optional[str] = Field(None, q=['snapshot__url__icontains', 'snapshot__title__icontains', 'snapshot__tags__name__icontains', 'extractor', 'output__icontains', 'id__startswith', 'abid__icontains', 'snapshot__id__startswith', 'snapshot__abid__icontains', 'snapshot__timestamp__startswith'])
snapshot_id: Optional[str] = Field(None, q=['snapshot__id__startswith', 'snapshot__abid__icontains', 'snapshot__timestamp__startswith']) snapshot_id: Optional[str] = Field(None, q=['snapshot__id__startswith', 'snapshot__abid__icontains', 'snapshot__timestamp__startswith'])
snapshot_url: Optional[str] = Field(None, q='snapshot__url__icontains') snapshot_url: Optional[str] = Field(None, q='snapshot__url__icontains')
snapshot_tag: Optional[str] = Field(None, q='snapshot__tags__name__icontains') snapshot_tag: Optional[str] = Field(None, q='snapshot__tags__name__icontains')
@ -157,8 +156,8 @@ def get_archiveresults(request, filters: ArchiveResultFilterSchema = Query(...))
@router.get("/archiveresult/{archiveresult_id}", response=ArchiveResultSchema, url_name="get_archiveresult") @router.get("/archiveresult/{archiveresult_id}", response=ArchiveResultSchema, url_name="get_archiveresult")
def get_archiveresult(request, archiveresult_id: str): def get_archiveresult(request, archiveresult_id: str):
"""Get a specific ArchiveResult by pk, abid, or old_id.""" """Get a specific ArchiveResult by id or abid."""
return ArchiveResult.objects.get(Q(id__icontains=archiveresult_id) | Q(abid__icontains=archiveresult_id) | Q(old_id__icontains=archiveresult_id)) return ArchiveResult.objects.get(Q(id__icontains=archiveresult_id) | Q(abid__icontains=archiveresult_id))
# @router.post("/archiveresult", response=ArchiveResultSchema) # @router.post("/archiveresult", response=ArchiveResultSchema)
@ -193,7 +192,6 @@ class SnapshotSchema(Schema):
TYPE: str = 'core.models.Snapshot' TYPE: str = 'core.models.Snapshot'
id: UUID id: UUID
old_id: UUID
abid: str abid: str
modified: datetime modified: datetime
@ -251,9 +249,7 @@ class SnapshotSchema(Schema):
class SnapshotFilterSchema(FilterSchema): class SnapshotFilterSchema(FilterSchema):
id: Optional[str] = Field(None, q=['id__icontains', 'abid__icontains', 'old_id__icontains', 'timestamp__startswith']) id: Optional[str] = Field(None, q=['id__icontains', 'abid__icontains', 'timestamp__startswith'])
old_id: Optional[str] = Field(None, q='old_id__icontains')
abid: Optional[str] = Field(None, q='abid__icontains') abid: Optional[str] = Field(None, q='abid__icontains')
created_by_id: str = Field(None, q='created_by_id') created_by_id: str = Field(None, q='created_by_id')
@ -266,7 +262,7 @@ class SnapshotFilterSchema(FilterSchema):
modified__gte: datetime = Field(None, q='modified__gte') modified__gte: datetime = Field(None, q='modified__gte')
modified__lt: datetime = Field(None, q='modified__lt') modified__lt: datetime = Field(None, q='modified__lt')
search: Optional[str] = Field(None, q=['url__icontains', 'title__icontains', 'tags__name__icontains', 'id__icontains', 'abid__icontains', 'old_id__icontains', 'timestamp__startswith']) search: Optional[str] = Field(None, q=['url__icontains', 'title__icontains', 'tags__name__icontains', 'id__icontains', 'abid__icontains', 'timestamp__startswith'])
url: Optional[str] = Field(None, q='url') url: Optional[str] = Field(None, q='url')
tag: Optional[str] = Field(None, q='tags__name') tag: Optional[str] = Field(None, q='tags__name')
title: Optional[str] = Field(None, q='title__icontains') title: Optional[str] = Field(None, q='title__icontains')
@ -293,12 +289,12 @@ def get_snapshot(request, snapshot_id: str, with_archiveresults: bool=True):
request.with_archiveresults = with_archiveresults request.with_archiveresults = with_archiveresults
snapshot = None snapshot = None
try: try:
snapshot = Snapshot.objects.get(Q(abid__startswith=snapshot_id) | Q(id__startswith=snapshot_id) | Q(old_id__startswith=snapshot_id) | Q(timestamp__startswith=snapshot_id)) snapshot = Snapshot.objects.get(Q(abid__startswith=snapshot_id) | Q(id__startswith=snapshot_id) | Q(timestamp__startswith=snapshot_id))
except Snapshot.DoesNotExist: except Snapshot.DoesNotExist:
pass pass
try: try:
snapshot = snapshot or Snapshot.objects.get(Q(abid__icontains=snapshot_id) | Q(id__icontains=snapshot_id) | Q(old_id__icontains=snapshot_id)) snapshot = snapshot or Snapshot.objects.get(Q(abid__icontains=snapshot_id) | Q(id__icontains=snapshot_id))
except Snapshot.DoesNotExist: except Snapshot.DoesNotExist:
pass pass
@ -338,7 +334,6 @@ class TagSchema(Schema):
TYPE: str = 'core.models.Tag' TYPE: str = 'core.models.Tag'
id: UUID id: UUID
old_id: str
abid: str abid: str
modified: datetime modified: datetime
@ -351,10 +346,6 @@ class TagSchema(Schema):
num_snapshots: int num_snapshots: int
snapshots: List[SnapshotSchema] snapshots: List[SnapshotSchema]
@staticmethod
def resolve_old_id(obj):
return str(obj.old_id)
@staticmethod @staticmethod
def resolve_created_by_id(obj): def resolve_created_by_id(obj):
return str(obj.created_by_id) return str(obj.created_by_id)
@ -386,11 +377,6 @@ def get_tag(request, tag_id: str, with_snapshots: bool=True):
request.with_snapshots = with_snapshots request.with_snapshots = with_snapshots
request.with_archiveresults = False request.with_archiveresults = False
tag = None tag = None
try:
tag = tag or Tag.objects.get(old_id__icontains=tag_id)
except (Tag.DoesNotExist, ValidationError, ValueError):
pass
try: try:
tag = Tag.objects.get(abid__icontains=tag_id) tag = Tag.objects.get(abid__icontains=tag_id)
except (Tag.DoesNotExist, ValidationError): except (Tag.DoesNotExist, ValidationError):

View file

@ -240,8 +240,8 @@ class ArchiveResultInline(admin.TabularInline):
# fk_name = 'snapshot' # fk_name = 'snapshot'
extra = 0 extra = 0
sort_fields = ('end_ts', 'extractor', 'output', 'status', 'cmd_version') sort_fields = ('end_ts', 'extractor', 'output', 'status', 'cmd_version')
readonly_fields = ('result_id', 'completed', 'extractor', 'command', 'version') readonly_fields = ('id', 'result_id', 'completed', 'command', 'version')
fields = ('id', 'start_ts', 'end_ts', *readonly_fields, 'cmd', 'cmd_version', 'pwd', 'created_by', 'status', 'output') fields = ('start_ts', 'end_ts', *readonly_fields, 'extractor', 'cmd', 'cmd_version', 'pwd', 'created_by', 'status', 'output')
# exclude = ('id',) # exclude = ('id',)
ordering = ('end_ts',) ordering = ('end_ts',)
show_change_link = True show_change_link = True
@ -273,7 +273,7 @@ class ArchiveResultInline(admin.TabularInline):
snapshot = self.get_parent_object_from_request(request) snapshot = self.get_parent_object_from_request(request)
# import ipdb; ipdb.set_trace() # import ipdb; ipdb.set_trace()
formset.form.base_fields['id'].widget = formset.form.base_fields['id'].hidden_widget() # formset.form.base_fields['id'].widget = formset.form.base_fields['id'].hidden_widget()
# default values for new entries # default values for new entries
formset.form.base_fields['status'].initial = 'succeeded' formset.form.base_fields['status'].initial = 'succeeded'
@ -351,13 +351,13 @@ class SnapshotActionForm(ActionForm):
@admin.register(Snapshot, site=archivebox_admin) @admin.register(Snapshot, site=archivebox_admin)
class SnapshotAdmin(SearchResultsAdminMixin, ABIDModelAdmin): class SnapshotAdmin(SearchResultsAdminMixin, ABIDModelAdmin):
list_display = ('added', 'title_str', 'files', 'size', 'url_str') list_display = ('created', 'title_str', 'files', 'size', 'url_str')
sort_fields = ('title_str', 'url_str', 'added') sort_fields = ('title_str', 'url_str', 'created')
readonly_fields = ('tags_str', 'timestamp', 'admin_actions', 'status_info', 'bookmarked', 'created', 'added', 'updated', 'modified', 'API', 'link_dir') readonly_fields = ('tags_str', 'timestamp', 'admin_actions', 'status_info', 'bookmarked', 'created', 'created', 'updated', 'modified', 'API', 'link_dir')
search_fields = ('id', 'url', 'abid', 'old_id', 'timestamp', 'title', 'tags__name') search_fields = ('id', 'url', 'abid', 'timestamp', 'title', 'tags__name')
list_filter = ('added', 'updated', 'archiveresult__status', 'created_by', 'tags__name') list_filter = ('created', 'updated', 'archiveresult__status', 'created_by', 'tags__name')
fields = ('url', 'created_by', 'title',*readonly_fields) fields = ('url', 'created_by', 'title',*readonly_fields)
ordering = ['-added'] ordering = ['-created']
actions = ['add_tags', 'remove_tags', 'update_titles', 'update_snapshots', 'resnapshot_snapshot', 'overwrite_snapshots', 'delete_snapshots'] actions = ['add_tags', 'remove_tags', 'update_titles', 'update_snapshots', 'resnapshot_snapshot', 'overwrite_snapshots', 'delete_snapshots']
inlines = [TagInline, ArchiveResultInline] inlines = [TagInline, ArchiveResultInline]
list_per_page = min(max(5, CONFIG.SNAPSHOTS_PER_PAGE), 5000) list_per_page = min(max(5, CONFIG.SNAPSHOTS_PER_PAGE), 5000)
@ -391,12 +391,6 @@ class SnapshotAdmin(SearchResultsAdminMixin, ABIDModelAdmin):
except (Snapshot.DoesNotExist, ValidationError): except (Snapshot.DoesNotExist, ValidationError):
pass pass
try:
snapshot = snapshot or Snapshot.objects.get(old_id=object_id)
except (Snapshot.DoesNotExist, Snapshot.MultipleObjectsReturned, ValidationError):
pass
if snapshot: if snapshot:
object_id = str(snapshot.id) object_id = str(snapshot.id)
@ -690,7 +684,7 @@ class ArchiveResultAdmin(ABIDModelAdmin):
list_display = ('start_ts', 'snapshot_info', 'tags_str', 'extractor', 'cmd_str', 'status', 'output_str') list_display = ('start_ts', 'snapshot_info', 'tags_str', 'extractor', 'cmd_str', 'status', 'output_str')
sort_fields = ('start_ts', 'extractor', 'status') sort_fields = ('start_ts', 'extractor', 'status')
readonly_fields = ('cmd_str', 'snapshot_info', 'tags_str', 'created', 'modified', 'API', 'output_summary') readonly_fields = ('cmd_str', 'snapshot_info', 'tags_str', 'created', 'modified', 'API', 'output_summary')
search_fields = ('id', 'old_id', 'abid', 'snapshot__url', 'extractor', 'output', 'cmd_version', 'cmd', 'snapshot__timestamp') search_fields = ('id', 'abid', 'snapshot__url', 'extractor', 'output', 'cmd_version', 'cmd', 'snapshot__timestamp')
fields = ('snapshot', 'extractor', 'status', 'output', 'pwd', 'start_ts', 'end_ts', 'created_by', 'cmd_version', 'cmd', *readonly_fields) fields = ('snapshot', 'extractor', 'status', 'output', 'pwd', 'start_ts', 'end_ts', 'created_by', 'cmd_version', 'cmd', *readonly_fields)
autocomplete_fields = ['snapshot'] autocomplete_fields = ['snapshot']

View file

@ -0,0 +1,52 @@
# Generated by Django 5.1 on 2024-09-04 09:00
import abid_utils.models
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0069_alter_archiveresult_created_alter_snapshot_added_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterField(
model_name='archiveresult',
name='created_by',
field=models.ForeignKey(default=abid_utils.models.get_or_create_system_user_pk, on_delete=django.db.models.deletion.CASCADE, related_name='archiveresult_set', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='snapshot',
name='added',
field=abid_utils.models.AutoDateTimeField(db_index=True, default=None),
),
migrations.AlterField(
model_name='snapshot',
name='created',
field=abid_utils.models.AutoDateTimeField(db_index=True, default=None),
),
migrations.AlterField(
model_name='snapshot',
name='created_by',
field=models.ForeignKey(default=abid_utils.models.get_or_create_system_user_pk, on_delete=django.db.models.deletion.CASCADE, related_name='snapshot_set', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='snapshot',
name='id',
field=models.UUIDField(default=None, primary_key=True, serialize=False, unique=True),
),
migrations.AlterField(
model_name='snapshot',
name='old_id',
field=models.UUIDField(default=None, editable=False, unique=True),
),
migrations.AlterField(
model_name='tag',
name='created',
field=abid_utils.models.AutoDateTimeField(db_index=True, default=None),
),
]

View file

@ -0,0 +1,65 @@
# Generated by Django 5.1 on 2024-09-04 23:23
import abid_utils.models
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0070_alter_archiveresult_created_by_alter_snapshot_added_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RemoveField(
model_name='archiveresult',
name='old_id',
),
migrations.RemoveField(
model_name='snapshot',
name='old_id',
),
migrations.RemoveField(
model_name='tag',
name='old_id',
),
migrations.AlterField(
model_name='archiveresult',
name='created',
field=abid_utils.models.AutoDateTimeField(db_index=True, default=None),
),
migrations.AlterField(
model_name='archiveresult',
name='created_by',
field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, related_name='archiveresult_set', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='archiveresult',
name='id',
field=models.UUIDField(default=None, editable=False, primary_key=True, serialize=False, unique=True, verbose_name='ID'),
),
migrations.AlterField(
model_name='snapshot',
name='created_by',
field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, related_name='snapshot_set', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='snapshot',
name='id',
field=models.UUIDField(default=None, editable=False, primary_key=True, serialize=False, unique=True, verbose_name='ID'),
),
migrations.AlterField(
model_name='tag',
name='created_by',
field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, related_name='tag_set', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='tag',
name='id',
field=models.UUIDField(default=None, editable=False, primary_key=True, serialize=False, unique=True, verbose_name='ID'),
),
]

View file

@ -54,13 +54,15 @@ class Tag(ABIDModel):
abid_ts_src = 'self.created' abid_ts_src = 'self.created'
abid_uri_src = 'self.slug' abid_uri_src = 'self.slug'
abid_subtype_src = '"03"' abid_subtype_src = '"03"'
abid_rand_src = 'self.old_id' abid_rand_src = 'self.id'
old_id = models.BigIntegerField(unique=True, default=rand_int_id, serialize=False, verbose_name='Old ID') # legacy PK id = models.UUIDField(primary_key=True, default=None, null=False, editable=False, unique=True, verbose_name='ID')
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
abid = ABIDField(prefix=abid_prefix) abid = ABIDField(prefix=abid_prefix)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, default=None, null=False, related_name='tag_set')
created = AutoDateTimeField(default=None, null=False, db_index=True)
modified = models.DateTimeField(auto_now=True)
name = models.CharField(unique=True, blank=False, max_length=100) name = models.CharField(unique=True, blank=False, max_length=100)
slug = models.SlugField(unique=True, blank=False, max_length=100, editable=False) slug = models.SlugField(unique=True, blank=False, max_length=100, editable=False)
# slug is autoset on save from name, never set it manually # slug is autoset on save from name, never set it manually
@ -74,10 +76,6 @@ class Tag(ABIDModel):
def __str__(self): def __str__(self):
return self.name return self.name
# @property
# def old_id(self):
# return self.id
def slugify(self, tag, i=None): def slugify(self, tag, i=None):
slug = slugify(tag) slug = slugify(tag)
if i is not None: if i is not None:
@ -133,16 +131,15 @@ class SnapshotManager(models.Manager):
class Snapshot(ABIDModel): class Snapshot(ABIDModel):
abid_prefix = 'snp_' abid_prefix = 'snp_'
abid_ts_src = 'self.added' abid_ts_src = 'self.created'
abid_uri_src = 'self.url' abid_uri_src = 'self.url'
abid_subtype_src = '"01"' abid_subtype_src = '"01"'
abid_rand_src = 'self.old_id' abid_rand_src = 'self.id'
old_id = models.UUIDField(default=None, null=False, editable=False, unique=True) # legacy pk id = models.UUIDField(primary_key=True, default=None, null=False, editable=False, unique=True, verbose_name='ID')
id = models.UUIDField(default=None, null=False, primary_key=True, editable=True, unique=True)
abid = ABIDField(prefix=abid_prefix) abid = ABIDField(prefix=abid_prefix)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, default=get_or_create_system_user_pk, related_name='snapshot_set') created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, default=None, null=False, related_name='snapshot_set')
created = AutoDateTimeField(default=None, null=False, db_index=True) created = AutoDateTimeField(default=None, null=False, db_index=True)
modified = models.DateTimeField(auto_now=True) modified = models.DateTimeField(auto_now=True)
@ -161,7 +158,6 @@ class Snapshot(ABIDModel):
objects = SnapshotManager() objects = SnapshotManager()
def __repr__(self) -> str: def __repr__(self) -> str:
title = (self.title_stripped or '-')[:64] title = (self.title_stripped or '-')[:64]
return f'[{self.timestamp}] {self.url[:64]} ({title})' return f'[{self.timestamp}] {self.url[:64]} ({title})'
@ -414,7 +410,7 @@ class ArchiveResult(ABIDModel):
abid_ts_src = 'self.snapshot.added' abid_ts_src = 'self.snapshot.added'
abid_uri_src = 'self.snapshot.url' abid_uri_src = 'self.snapshot.url'
abid_subtype_src = 'self.extractor' abid_subtype_src = 'self.extractor'
abid_rand_src = 'self.old_id' abid_rand_src = 'self.id'
EXTRACTOR_CHOICES = ( EXTRACTOR_CHOICES = (
('htmltotext', 'htmltotext'), ('htmltotext', 'htmltotext'),
@ -438,13 +434,11 @@ class ArchiveResult(ABIDModel):
("skipped", "skipped") ("skipped", "skipped")
] ]
old_id = models.BigIntegerField(default=rand_int_id, serialize=False, verbose_name='Old ID') id = models.UUIDField(primary_key=True, default=None, null=False, editable=False, unique=True, verbose_name='ID')
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True, unique=True, verbose_name='ID')
abid = ABIDField(prefix=abid_prefix) abid = ABIDField(prefix=abid_prefix)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, default=get_or_create_system_user_pk, related_name='archiveresult_set') created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, default=None, null=False, related_name='archiveresult_set')
created = AutoDateTimeField(default=timezone.now, db_index=True) created = AutoDateTimeField(default=None, null=False, db_index=True)
modified = models.DateTimeField(auto_now=True) modified = models.DateTimeField(auto_now=True)
snapshot = models.ForeignKey(Snapshot, on_delete=models.CASCADE, to_field='id', db_column='snapshot_id') snapshot = models.ForeignKey(Snapshot, on_delete=models.CASCADE, to_field='id', db_column='snapshot_id')

View file

@ -285,17 +285,17 @@ class SnapshotView(View):
# ulid = slug.split('_', 1)[-1] # ulid = slug.split('_', 1)[-1]
# try: # try:
# try: # try:
# snapshot = snapshot or Snapshot.objects.get(Q(abid=ulid) | Q(id=ulid) | Q(old_id=ulid)) # snapshot = snapshot or Snapshot.objects.get(Q(abid=ulid) | Q(id=ulid))
# except Snapshot.DoesNotExist: # except Snapshot.DoesNotExist:
# pass # pass
# try: # try:
# snapshot = Snapshot.objects.get(Q(abid__startswith=slug) | Q(abid__startswith=Snapshot.abid_prefix + slug) | Q(id__startswith=slug) | Q(old_id__startswith=slug)) # snapshot = Snapshot.objects.get(Q(abid__startswith=slug) | Q(abid__startswith=Snapshot.abid_prefix + slug) | Q(id__startswith=slug))
# except (Snapshot.DoesNotExist, Snapshot.MultipleObjectsReturned): # except (Snapshot.DoesNotExist, Snapshot.MultipleObjectsReturned):
# pass # pass
# try: # try:
# snapshot = snapshot or Snapshot.objects.get(Q(abid__icontains=snapshot_id) | Q(id__icontains=snapshot_id) | Q(old_id__icontains=snapshot_id)) # snapshot = snapshot or Snapshot.objects.get(Q(abid__icontains=snapshot_id) | Q(id__icontains=snapshot_id))
# except Snapshot.DoesNotExist: # except Snapshot.DoesNotExist:
# pass # pass
# return redirect(f'/archive/{snapshot.timestamp}/index.html') # return redirect(f'/archive/{snapshot.timestamp}/index.html')
@ -308,7 +308,7 @@ class SnapshotView(View):
# try exact match on full url / ABID first # try exact match on full url / ABID first
snapshot = Snapshot.objects.get( snapshot = Snapshot.objects.get(
Q(url='http://' + path) | Q(url='https://' + path) | Q(id__startswith=path) Q(url='http://' + path) | Q(url='https://' + path) | Q(id__startswith=path)
| Q(abid__icontains=path) | Q(id__icontains=path) | Q(old_id__icontains=path) | Q(abid__icontains=path) | Q(id__icontains=path)
) )
except Snapshot.DoesNotExist: except Snapshot.DoesNotExist:
# fall back to match on exact base_url # fall back to match on exact base_url
@ -352,7 +352,7 @@ class SnapshotView(View):
) )
for snap in Snapshot.objects.filter( for snap in Snapshot.objects.filter(
Q(url__startswith='http://' + base_url(path)) | Q(url__startswith='https://' + base_url(path)) Q(url__startswith='http://' + base_url(path)) | Q(url__startswith='https://' + base_url(path))
| Q(abid__icontains=path) | Q(id__icontains=path) | Q(old_id__icontains=path) | Q(abid__icontains=path) | Q(id__icontains=path)
).only('url', 'timestamp', 'title', 'added').order_by('-added') ).only('url', 'timestamp', 'title', 'added').order_by('-added')
) )
return HttpResponse( return HttpResponse(

View file

@ -192,7 +192,6 @@ class Link:
if extended: if extended:
info.update({ info.update({
'snapshot_id': self.snapshot_id, 'snapshot_id': self.snapshot_id,
'snapshot_old_id': self.snapshot_old_id,
'snapshot_abid': self.snapshot_abid, 'snapshot_abid': self.snapshot_abid,
'link_dir': self.link_dir, 'link_dir': self.link_dir,
@ -266,16 +265,12 @@ class Link:
@cached_property @cached_property
def snapshot(self): def snapshot(self):
from core.models import Snapshot from core.models import Snapshot
return Snapshot.objects.only('id', 'old_id', 'abid').get(url=self.url) return Snapshot.objects.only('id', 'abid').get(url=self.url)
@cached_property @cached_property
def snapshot_id(self): def snapshot_id(self):
return str(self.snapshot.pk) return str(self.snapshot.pk)
@cached_property
def snapshot_old_id(self):
return str(self.snapshot.old_id)
@cached_property @cached_property
def snapshot_abid(self): def snapshot_abid(self):
return str(self.snapshot.ABID) return str(self.snapshot.ABID)