from django.conf import settings from django.db import migrations, models from django.db.models import Q import django.db.models.deletion import apps.content.current def seo_fields(): return [ ("seo_title", models.CharField(blank=True, max_length=120, verbose_name="SEO title")), ("seo_description", models.CharField(blank=True, max_length=300, verbose_name="SEO description")), ("seo_image", models.ImageField(blank=True, upload_to=apps.content.current.content_upload_path, verbose_name="Social sharing image")), ("seo_image_alt", models.CharField(blank=True, max_length=180, verbose_name="Social image description")), ] def single_id(): return [("id", models.PositiveSmallIntegerField(default=1, editable=False, primary_key=True, serialize=False))] def strings(*names): return [(name, models.CharField(blank=True, max_length=length)) for name, length in names] def image(name): return (name, models.ImageField(blank=True, upload_to=apps.content.current.content_upload_path)) def copy_image(target, name, asset_id, alt, assets): asset = assets.get(asset_id) if asset: setattr(target, name, asset.file.name) setattr(target, f"{name}_alt", alt or asset.alt) def populate_current_content(apps, schema_editor): db = schema_editor.connection.alias SiteContent = apps.get_model("content", "SiteContent") HomeContent = apps.get_model("content", "HomeContent") AboutContent = apps.get_model("content", "AboutContent") AboutItem = apps.get_model("content", "AboutItem") ListingContent = apps.get_model("content", "ListingContent") SiteIdentity = apps.get_model("content", "SiteIdentity") EditorialPage = apps.get_model("content", "EditorialPage") MediaAsset = apps.get_model("content", "MediaAsset") assets = {asset.pk: asset for asset in MediaAsset.objects.using(db).all()} site = SiteContent(id=1) identity = SiteIdentity.objects.using(db).filter(pk=1).first() revision = identity.published_revision if identity and identity.published_revision_id else None if revision: for field in ("official_name", "short_name", "faculty_subline", "description", "footer_copy", "seo_title", "seo_description"): setattr(site, field, getattr(revision, field)) copy_image(site, "logo", revision.logo_id, revision.logo_alt, assets) copy_image(site, "seo_image", revision.seo_image_id, "Social sharing image", assets) for nav in revision.navigation: if nav.get("route_id") in ("home", "about", "events", "blog"): setattr(site, f"nav_{nav['route_id']}", nav.get("label", "")) for contact in revision.contacts: kind, url = contact.get("kind"), contact.get("url", "") if kind == "email": site.email = url.removeprefix("mailto:") elif kind == "phone": site.phone = url.removeprefix("tel:") elif kind in ("telegram", "instagram", "website"): setattr(site, f"{kind}_url", url) site.save(using=db) pages = {page.slug: page for page in EditorialPage.objects.using(db).all()} for slug, Model in (("home", HomeContent), ("about", AboutContent), ("events", ListingContent), ("blog", ListingContent)): page = pages.get(slug) revision = page.published_revision if page and page.published_revision_id else None record = Model(id=1) if slug in ("home", "about") else Model(slug=slug) if revision: for field in ("title", "intro", "seo_title", "seo_description"): setattr(record, field, getattr(revision, field)) copy_image(record, "seo_image", revision.seo_image_id, "Social sharing image", assets) if slug in ("home", "about"): sections = list(revision.sections.using(db).all()) for section in sections: prefix = section.key if slug == "home": if prefix in ("hero", "participation"): setattr(record, f"{prefix}_heading", section.heading) setattr(record, f"{prefix}_body", section.body) setattr(record, f"{prefix}_action_label", (section.primary_action or {}).get("label", "")) copy_image(record, f"{prefix}_image", section.image_id, section.image_alt, assets) elif prefix in ("featured_events", "featured_posts"): setattr(record, f"{prefix}_heading", section.heading) setattr(record, f"{prefix}_intro", section.intro) setattr(record, f"{prefix}_link_label", section.all_link_label) elif prefix in ("identity", "mission", "activities", "values", "committees", "affiliations", "faq", "contact"): setattr(record, f"{prefix}_heading", section.heading) if hasattr(record, f"{prefix}_body"): setattr(record, f"{prefix}_body", section.body) record.save(using=db) if revision and slug == "about": for section in sections: if section.key in ("mission", "activities", "values", "committees", "affiliations", "faq"): for position, entry in enumerate(section.entries): AboutItem.objects.using(db).create(about_id=1, kind=section.key, title=entry["title"], text=entry["text"], position=position) def preserve_editor_access(apps, schema_editor): db = schema_editor.connection.alias ContentType = apps.get_model("contenttypes", "ContentType") Permission = apps.get_model("auth", "Permission") Group = apps.get_model("auth", "Group") User = apps.get_model(*settings.AUTH_USER_MODEL.split(".")) models_to_change = ("sitecontent", "homecontent", "aboutcontent", "aboutitem", "listingcontent") new_permissions = [] for model in models_to_change: content_type, _ = ContentType.objects.using(db).get_or_create(app_label="content", model=model) permission, _ = Permission.objects.using(db).get_or_create( content_type=content_type, codename=f"change_{model}", defaults={"name": f"Can change {model}"} ) new_permissions.append(permission) for group in Group.objects.using(db).filter(name__in=("content_editor", "content_publisher")): group.permissions.add(*new_permissions) old_permissions = Permission.objects.using(db).filter( content_type__app_label="content", codename__in=("change_content_draft", "publish_content") ) for user in User.objects.using(db).filter(user_permissions__in=old_permissions).distinct(): user.user_permissions.add(*new_permissions) class Migration(migrations.Migration): dependencies = [ ("content", "0003_content_groups"), ("auth", "0012_alter_user_first_name_max_length"), ("contenttypes", "0002_remove_content_type_name"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( name="SiteContent", fields=single_id() + seo_fields() + strings( ("official_name", 180), ("short_name", 80), ("faculty_subline", 140), ("logo_alt", 180), ("phone", 30), ("footer_copy", 300) ) + [ ("description", models.TextField(blank=True)), image("logo"), ("nav_home", models.CharField(default="Home", max_length=80, verbose_name="Home navigation label")), ("nav_about", models.CharField(default="About", max_length=80, verbose_name="About navigation label")), ("nav_events", models.CharField(default="Events", max_length=80, verbose_name="Events navigation label")), ("nav_blog", models.CharField(default="Blog", max_length=80, verbose_name="Blog navigation label")), ("email", models.EmailField(blank=True, max_length=254)), ("telegram_url", models.URLField(blank=True)), ("instagram_url", models.URLField(blank=True)), ("website_url", models.URLField(blank=True)), ("address", models.TextField(blank=True)), ], options={"verbose_name": "Site content"}, ), migrations.CreateModel( name="HomeContent", fields=single_id() + seo_fields() + strings( ("title", 180), ("hero_heading", 120), ("hero_image_alt", 180), ("hero_action_label", 40), ("featured_events_heading", 120), ("featured_events_intro", 300), ("featured_events_link_label", 40), ("featured_posts_heading", 120), ("featured_posts_intro", 300), ("featured_posts_link_label", 40), ("participation_heading", 120), ("participation_image_alt", 180), ("participation_action_label", 40) ) + [ ("intro", models.TextField(blank=True)), ("hero_body", models.TextField(blank=True)), image("hero_image"), ("participation_body", models.TextField(blank=True)), image("participation_image"), ], options={"verbose_name": "Home page content"}, ), migrations.CreateModel( name="AboutContent", fields=single_id() + seo_fields() + strings( ("title", 180), ("identity_heading", 120), ("mission_heading", 120), ("activities_heading", 120), ("values_heading", 120), ("committees_heading", 120), ("affiliations_heading", 120), ("contact_heading", 120) ) + [ ("faq_heading", models.CharField(blank=True, max_length=120, verbose_name="FAQ heading")), ("intro", models.TextField(blank=True)), *[(f"{name}_body", models.TextField(blank=True)) for name in ("identity", "mission", "activities", "values", "committees", "affiliations")], ], options={"verbose_name": "About page content"}, ), migrations.CreateModel( name="ListingContent", fields=[("slug", models.CharField(choices=[("events", "Events"), ("blog", "Blog")], max_length=10, primary_key=True, serialize=False))] + seo_fields() + strings(("title", 180)) + [("intro", models.TextField(blank=True))], options={"verbose_name": "Listing page content", "verbose_name_plural": "Listing page content"}, ), migrations.CreateModel( name="AboutItem", fields=[ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), ("kind", models.CharField(choices=[(key, key.title()) for key in ("mission", "activities", "values", "committees", "affiliations", "faq")], max_length=20)), ("title", models.CharField(max_length=120)), ("text", models.TextField()), ("position", models.PositiveSmallIntegerField(default=0)), ("about", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name="items", to="content.aboutcontent")), ], options={"verbose_name": "About page item", "ordering": ["kind", "position", "pk"]}, ), migrations.AddConstraint(model_name="sitecontent", constraint=models.CheckConstraint(condition=Q(id=1), name="content_current_single_site")), migrations.AddConstraint(model_name="homecontent", constraint=models.CheckConstraint(condition=Q(id=1), name="content_current_single_home")), migrations.AddConstraint(model_name="aboutcontent", constraint=models.CheckConstraint(condition=Q(id=1), name="content_current_single_about")), migrations.RunPython(populate_current_content, migrations.RunPython.noop), migrations.RunPython(preserve_editor_access, migrations.RunPython.noop), ]