class Video(ThumbnailAbstract): """A video""" subscription = models.ForeignKey(Subscription) user = models.ForeignKey(settings.AUTH_USER_MODEL) viewed = models.BooleanField(default=False) buckets = RelatedSetField(Bucket) # from video endpoint youtube_id = models.CharField(max_length=200) # id published_at = models.DateTimeField() # calculate id based on user ID + video ID so we can get by keys later id = ComputedCharField( lambda self: create_composite_key(str(self.user_id), self.youtube_id), primary_key=True, max_length=200) ordering_key = ComputedCharField(lambda self: create_composite_key( self.published_at.isoformat(" "), self.youtube_id), max_length=200) objects = VideoQuerySet.as_manager() class Meta: ordering = ["ordering_key"] @property def html_snippet(self): tmpl = get_template("subscribae/includes/videos.html") return tmpl.render({"video": self}) def add_titles(self): """Fetches titles and descriptions for Video""" from subscribae.utils import video_add_titles return list(video_add_titles([self]))[0]
class Subscription(ThumbnailAbstract): """A subscription that belongs to a user""" user = models.ForeignKey(settings.AUTH_USER_MODEL) last_update = models.DateTimeField() last_viewed = models.DateTimeField(null=True) last_watched_video = models.CharField(max_length=200) # from subscription endpoint channel_id = models.CharField( max_length=200) # snippet.resourceId.channelId # from channel endpoint upload_playlist = models.CharField( max_length=200) # contentDetails.relatedPlaylists.uploads # calculate id based on user ID + channel ID so we can get by keys later id = ComputedCharField( lambda self: create_composite_key(str(self.user_id), self.channel_id), primary_key=True, max_length=200) objects = SubscriptionQuerySet.as_manager() def __unicode__(self): tmpl = get_template("subscribae/models/subscription.html") return tmpl.render({"object": self}) def __repr__(self): return "<Subscription {}>".format( self.channel_id.encode("utf-8", "ignore")) def add_titles(self): """Fetches titles and descriptions for Subscription""" from subscribae.utils import subscription_add_titles return list(subscription_add_titles([self]))[0]
class ComputedFieldModel(models.Model): def computer(self): return "%s_%s" % (self.int_field, self.char_field) int_field = models.IntegerField() char_field = models.CharField(max_length=50) test_field = ComputedCharField(computer, max_length=50) class Meta: app_label = "djangae"
def __call__(self, cls): """ Dynamically adds pagination fields to a model depending on the orderings you specify """ for ordering in self.orderings: new_field_name = _field_name_for_ordering(ordering) ComputedCharField(partial(generator, ordering), max_length=500, editable=False).contribute_to_class( cls, new_field_name) return cls
class GaeAbstractBaseUser(AbstractBaseUser): """ Absract base class for creating a User model which works with the App Engine users API. """ username = CharOrNoneField( # This stores the Google user_id, or custom username for non-Google-based users. # We allow it to be null so that Google-based users can be pre-created before they log in. _('User ID'), max_length=21, unique=True, blank=True, null=True, default=None, validators=[validate_google_user_id]) first_name = models.CharField(_('first name'), max_length=30, blank=True) last_name = models.CharField(_('last name'), max_length=30, blank=True) # Email addresses are case sensitive, but many email systems and many people treat them as if # they're not. We must store the case-preserved email address to ensure that sending emails # always works, but we must be able to query for them case insensitively and therefore we must # enforce uniqueness on a case insensitive basis, hence these 2 fields email = models.EmailField(_('email address')) # The null-able-ness of the email_lower is only to deal with when an email address moves between # Google Accounts and therefore we need to wipe it without breaking the unique constraint. email_lower = ComputedCharField(_get_email_lower, max_length=email.max_length, unique=True, null=True) is_staff = models.BooleanField( _('staff status'), default=False, help_text=_( 'Designates whether the user can log into this admin site.')) is_active = models.BooleanField( _('active'), default=True, help_text=_('Designates whether this user should be treated as ' 'active. Unselect this instead of deleting accounts.')) date_joined = models.DateTimeField(_('date joined'), default=timezone.now) USERNAME_FIELD = 'username' REQUIRED_FIELDS = ['email'] objects = GaeUserManager() class Meta: abstract = True def clean(self): # Only call up if username is not none. Parent's clean() stringifies # username blindly if self.get_username() is not None: super(GaeAbstractBaseUser, self).clean() def get_absolute_url(self): return "/users/%s/" % urlquote(self.username) def get_full_name(self): """ Returns the first_name plus the last_name, with a space in between. """ full_name = '%s %s' % (self.first_name, self.last_name) return full_name.strip() def get_short_name(self): "Returns the short name for the user." return self.first_name def email_user(self, subject, message, from_email=None): """ Sends an email to this User. """ send_mail(subject, message, from_email, [self.email]) def __str__(self): """ We have to override this as username is nullable. We either return the email address, or if there is a username, we return "email (username)". """ username = self.get_username() if username: return "{} ({})".format(six.text_type(self.email), six.text_type(username)) return six.text_type(self.email) def validate_unique(self, exclude=None): """ Check that the email address does not already exist by querying on email_lower. """ exclude = exclude or [] if "email_lower" not in exclude: # We do our own check using the email_lower field, so we don't need Django to query # on it as well exclude.append("email_lower") try: super(GaeAbstractBaseUser, self).validate_unique(exclude=exclude) except ValidationError as super_error: pass else: super_error = None if self.email and "email" not in exclude: existing = self.__class__.objects.filter( email_lower=self.email.lower()) if not self._state.adding: existing = existing.exclude(pk=self.pk) if existing.exists(): model_name = self._meta.verbose_name field_name = self._meta.get_field("email").verbose_name message = "%s with this %s already exists" % (model_name, field_name) error_dict = {"email": [message]} if super_error: super_error.update_error_dict(error_dict) raise super_error else: raise ValidationError(error_dict) elif super_error: raise
class MasterTranslation(models.Model): id = models.CharField(max_length=64, primary_key=True) text = models.TextField() text_for_ordering = ComputedCharField(lambda instance: instance.text[:500], max_length=500) plural_text = models.TextField(blank=True) hint = models.CharField(max_length=500, default="", blank=True) language_code = models.CharField( max_length=8, choices=settings.LANGUAGES, default=settings.LANGUAGE_CODE ) translations_by_language_code = JSONField() translations = RelatedSetField(Translation) translated_into_languages = SetField(models.CharField(max_length=8), editable=False) # Was this master translation updated or created by make messages? used_in_code_or_templates = models.BooleanField(default=False, blank=True, editable=False) # Were any groups specified in the trans tags? used_by_groups_in_code_or_templates = SetField(models.CharField(max_length=64), blank=True) # Record the ID of the last scan which updated this instance (if any) last_updated_by_scan_uuid = models.CharField(max_length=64, blank=True, default="") first_letter = models.CharField(max_length=1, editable=False) @property def is_plural(self): return bool(self.plural_text) def __unicode__(self): return u"{} ({}{})".format(self.text, self.language_code, ' plural' if self.is_plural else '') def __repr__(self): """ Define an ASCII string safe representation of the master translation. """ return str("{}".format(self.id)) def get_display(self): from fluent.trans import _get_trans result = _get_trans(self.text, self.hint) return result def text_for_language_code(self, lang_code): new_code = find_closest_supported_language(lang_code) if new_code not in self.translations_by_language_code.keys(): # we don't have a translation for this language return self.text translation_id = self.translations_by_language_code[new_code] translation = Translation.objects.get(id=translation_id) return translation.text @classmethod def find_by_groups(cls, groups): from .fields import find_installed_translatable_fields translatable_fields_by_model = find_installed_translatable_fields(with_groups=groups) # Go through all Translatable(Char|Text)Fields or TextFields marked with the specified group and get # all the master translation IDs which are set to them master_translation_ids = [] for model, fields in translatable_fields_by_model.items(): master_translation_ids.extend(chain(*model.objects.values_list(*[field.attname for field in fields]))) master_translation_ids = list(set(master_translation_ids)) # Now get all the master translations with a group specified in the templates master_translation_ids.extend( list(MasterTranslation.objects.filter(used_by_groups_in_code_or_templates__overlap=groups) .values_list("pk", flat=True)) ) # Make sure master translation ids don't include None values or duplicates master_translation_ids = set(master_translation_ids) master_translation_ids = master_translation_ids - {None} # Return them all! return MasterTranslation.objects.filter(pk__in=master_translation_ids) @classmethod def find_by_group(cls, group_name): return cls.find_by_groups([group_name]) @staticmethod def generate_key(text, hint, language_code): assert text assert hint is not None assert language_code result = md5() for x in (text.encode("utf-8"), hint.encode("utf-8"), language_code): result.update(x) return result.hexdigest() def save(self, *args, **kwargs): assert self.text assert self.language_code # Always store the first letter for querying self.first_letter = self.text.strip()[:1] # Generate the appropriate key on creation if self._state.adding: self.pk = MasterTranslation.generate_key( self.text, self.hint, self.language_code ) # If we are adding for the first time, then create a counterpart # translation for the master language. # Note that this Translation will be complete and correct only for the languages that # only require 2 plural forms - for others this language needs to be explicitly translated # or updated later. if self._state.adding: with transaction.atomic(xg=True): singular_form = get_plural_index(self.language_code, 1) plural_form = get_plural_index(self.language_code, 2) plurals = {singular_form: self.text} if self.is_plural: plurals[plural_form] = self.plural_text # if len(LANGUAGE_LOOKUPS[self.language_code].plurals_needed) > len(plurals): # FIXME: We can detect that we're dealing with a language that needs more plurals # What should we do? mark the translation as incomplete? # Don't create the translation object at all? new_trans = Translation.objects.create( master_translation=self, language_code=self.language_code, plural_texts=plurals, denorm_master_text=self.text, denorm_master_hint=self.hint ) self.translations_by_language_code[self.language_code] = new_trans.pk self.translations.add(new_trans) self.translated_into_languages = set(self.translations_by_language_code.keys()) return super(MasterTranslation, self).save(*args, **kwargs) else: # Otherwise just do a normal save self.translated_into_languages = set(self.translations_by_language_code.keys()) return super(MasterTranslation, self).save(*args, **kwargs) def create_or_update_translation(self, language_code, singular_text=None, plural_texts=None, validate=False): if language_code not in dict(settings.LANGUAGES).keys(): return ["'{}' is not included as a language in your settings file".format(language_code)] with transaction.atomic(xg=True): trans = None if language_code in self.translations_by_language_code: # We already have a translation for this language, update it! try: trans = Translation.objects.get(pk=self.translations_by_language_code[language_code]) created = False except Translation.DoesNotExist: trans = None if not trans: # OK, create the translation object and add it to the respective fields trans = Translation( master_translation_id=self.pk, language_code=language_code, denorm_master_hint=self.hint, denorm_master_text=self.text ) created = True if plural_texts: trans.plural_texts = plural_texts else: trans.text = singular_text if validate: errors = validate_translation_texts(trans, self) if errors: return errors trans.master_translation = self trans.save() if created: self.refresh_from_db() self.translations_by_language_code[language_code] = trans.pk self.translations.add(trans) self.save() class Meta: app_label = "fluent"