Source code for maceoutliner.users.models

import logging
from operator import itemgetter
from django.dispatch import receiver
from django.contrib.auth.models import AbstractUser
from django.urls import reverse
from django.db import models
from django.db.models.signals import post_save
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from django.db.models import Value, CharField, F
from fiction_outlines.models import TimeStampedModel
from fiction_outlines.models import (
    Series,
    Outline,
    Character,
    Location,
    Arc,
    ArcElementNode,
)
from fiction_outlines.models import (
    CharacterInstance,
    LocationInstance,
    StoryElementNode,
)

# With much gratitude to Simon Willison for providing this method:
# https://simonwillison.net/2018/Mar/25/combined-recent-additions/


logger = logging.getLogger("maceoutliner.users")


[docs]def combined_recent(limit, **kwargs): datetime_field = kwargs.pop("datetime_field", "created") querysets = [] for key, queryset in kwargs.items(): querysets.append( queryset.annotate( recent_changes_type=Value(key, output_field=CharField()) ).values("pk", "recent_changes_type", datetime_field) ) union_qs = querysets[0].union(*querysets[1:]) records = [] for row in union_qs.order_by("-{}".format(datetime_field))[:limit]: records.append( { "type": row["recent_changes_type"], "when": row[datetime_field], "pk": row["pk"], } ) # Now we bulk-load each object type in turn to_load = {} for record in records: to_load.setdefault(record["type"], []).append(record["pk"]) fetched = {} for key, pks in to_load.items(): for item in kwargs[key].filter(pk__in=pks): fetched[(key, item.pk)] = item # Annotate 'records' with loaded objects for record in records: record["object"] = fetched[(record["type"], record["pk"])] return records
# recent = combined_recent( # 20, # entry=Entry.objects.all(), # photo=Photo.objects.all(), # )
[docs]@python_2_unicode_compatible class User(TimeStampedModel, AbstractUser): # First Name and Last Name do not cover name patterns # around the globe. display_name = models.CharField( _("Display Name"), blank=True, null=True, max_length=255, help_text=_("🎶 What's your name, son? ALEXANDER HAMILTON"), ) bio = models.CharField( _("Bio"), blank=True, null=True, max_length=255, help_text=_("A few words about you."), ) homepage_url = models.URLField( _("Homepage"), blank=True, null=True, help_text=_("Your home on the web.") ) def __str__(self): return self.username
[docs] def get_absolute_url(self): return reverse("users:detail", kwargs={"username": self.username})
[docs] def prepare_history_query_set_dict(self, datetime_field="created"): query_set_dict = { "datetime_field": datetime_field, "series": Series.objects.all().prefetch_related("tags"), "characters": Character.objects.all().prefetch_related("tags", "series"), "locations": Location.objects.all().prefetch_related("tags", "series"), "outlines": Outline.objects.all() .select_related("series") .prefetch_related("tags", "arc_set", "storyelementnode_set"), "arcs": Arc.objects.all() .select_related("outline") .prefetch_related("arcelementnode_set"), "characterinstances": CharacterInstance.objects.filter( character__user=self ), } if datetime_field == "created": query_set_dict["locationinstances"] = LocationInstance.objects.filter( location__user=self ) query_set_dict["storynodes"] = ( StoryElementNode.objects.filter(outline__user=self) .select_related("outline") .prefetch_related( "assoc_characters", "assoc_locations", "arcelementnode_set" ) .exclude(story_element_type="root") ) query_set_dict["arcnodes"] = ( ArcElementNode.objects.filter(arc__outline__user=self) .select_related("story_element_node") .prefetch_related("assoc_characters", "assoc_locations") .exclude(arc_element_type="root") ) return query_set_dict
[docs] def get_recent_additions(self, num_results): """ Fetch a list of recent additions or edits a user has made across the site. """ query_set_dict = self.prepare_history_query_set_dict() return combined_recent(num_results, **query_set_dict)
[docs] def get_recent_edits(self, num_results): """ Fetch recent edits the user has made. """ query_set_dict = self.prepare_history_query_set_dict(datetime_field="modified") for key, item in query_set_dict.items(): if key != "datetime_field": query_set_dict[key] = item.exclude(created=F("modified")) return combined_recent(num_results, **query_set_dict)
[docs] def get_all_recent_changes(self, num_results): """ Fetch the results of recent additions and modifications, merge them, resort, and then return as a list of dicts. """ additions = self.get_recent_additions(num_results) for item in additions: item.update({"edit_type": "add"}) modifications = self.get_recent_edits(num_results) for item in modifications: item.update({"edit_type": "edit"}) all_changes_unsorted = additions + modifications sorted_changes = sorted( all_changes_unsorted, key=itemgetter("when"), reverse=True ) return sorted_changes[:num_results]
[docs]@receiver(post_save, sender=ArcElementNode) def update_arc_modify_based_on_arc_node(sender, instance, created, *args, **kwargs): """ If an ArcElement Node is edited after the initial addition to the arc, update the arc's last modified timestamp. """ if not created: arc = instance.arc arc.modified = instance.modified arc.save()
[docs]@receiver(post_save, sender=StoryElementNode) def update_outline_modify_based_on_story_node( sender, instance, created, *args, **kwargs ): """ If a StoryElementNode is edited after the initial addition to the outline, update the outline's last modified timestamp. """ if not created: outline = instance.outline outline.modified = instance.modified outline.save()