Protect published snapshots and concurrent edits
This commit is contained in:
@@ -31,6 +31,9 @@ class MediaAsset(models.Model):
|
||||
approved = models.BooleanField(default=False)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.file.name or f"Media asset {self.pk}"
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
for field in ("alt", "source", "license"):
|
||||
@@ -60,6 +63,11 @@ class MediaAsset(models.Model):
|
||||
self.bytes = size
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.pk:
|
||||
previous = type(self).objects.get(pk=self.pk)
|
||||
protected = ("file", "alt", "source", "license", "width", "height", "bytes")
|
||||
if previous.approved and (not self.approved or any(getattr(self, field) != getattr(previous, field) for field in protected)):
|
||||
raise ValidationError("Approved media is immutable.")
|
||||
self.full_clean()
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
@@ -77,6 +85,9 @@ class SiteIdentity(models.Model):
|
||||
("restore_content", "Can restore published content"),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return "Association site identity"
|
||||
|
||||
def clean(self):
|
||||
if self.published_revision_id and self.published_revision.site_id != self.pk:
|
||||
raise ValidationError("Published revision belongs to another site.")
|
||||
@@ -126,6 +137,8 @@ class SiteRevision(models.Model):
|
||||
protected = ("site_id", "number", "official_name", "short_name", "faculty_subline", "description", "logo_id", "logo_alt", "navigation", "contacts", "footer_copy", "seo_title", "seo_description", "seo_image_id", "created_by_id", "source_seed_key", "source_seed_version", "content_checksum")
|
||||
if any(getattr(self, field) != getattr(previous, field) for field in protected):
|
||||
raise ValidationError("Site revision content is immutable.")
|
||||
if previous.status == "published" and (self.status != previous.status or self.published_by_id != previous.published_by_id or self.published_at != previous.published_at):
|
||||
raise ValidationError("Published site revision metadata is immutable.")
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
|
||||
@@ -134,6 +147,9 @@ class EditorialPage(models.Model):
|
||||
page_kind = models.CharField(max_length=20, choices=[("sections", "Sections"), ("listing_intro", "Listing intro")])
|
||||
published_revision = models.ForeignKey("PageRevision", null=True, blank=True, on_delete=models.PROTECT, related_name="published_for")
|
||||
|
||||
def __str__(self):
|
||||
return self.slug
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
if self.slug not in PAGE_KINDS or self.page_kind != PAGE_KINDS[self.slug]:
|
||||
@@ -177,6 +193,8 @@ class PageRevision(models.Model):
|
||||
raise ValidationError("Page revision content is immutable.")
|
||||
if self.content_checksum != previous.content_checksum and previous.content_checksum:
|
||||
raise ValidationError("Page revision checksum is immutable.")
|
||||
if previous.status == "published" and (self.status != previous.status or self.published_by_id != previous.published_by_id or self.published_at != previous.published_at):
|
||||
raise ValidationError("Published page revision metadata is immutable.")
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
|
||||
@@ -218,7 +236,8 @@ class PageSection(models.Model):
|
||||
if self.key == "contact" and not self.visible:
|
||||
raise ValidationError("About contact must be visible.")
|
||||
validators.text(self.heading, 120 if self.key in {"hero", "identity"} else 90, required=True)
|
||||
validators.text(self.body, 1200 if slug == "about" else 600)
|
||||
body_limit = 500 if self.key == "hero" else 600 if self.key in {"participation", "identity"} else 1200
|
||||
validators.text(self.body, body_limit)
|
||||
validators.text(self.intro, 300)
|
||||
validators.text(self.all_link_label, 40)
|
||||
validators.text(self.image_alt, 180)
|
||||
|
||||
Reference in New Issue
Block a user