From 241066d4c278f1a2224211bfd97a1921a0e478ef Mon Sep 17 00:00:00 2001 From: =?utf8?q?Stefan=20K=C3=B6gl?= Date: Sat, 12 Dec 2015 11:06:18 +0100 Subject: [PATCH] Update to Django 1.9.1 --- mygpo/maintenance/merge.py | 5 +- mygpo/podcasts/models.py | 373 ++++++++++++++++++++++--------------------- mygpo/usersettings/models.py | 3 +- mygpo/votes/models.py | 2 +- requirements.txt | 2 +- 5 files changed, 197 insertions(+), 188 deletions(-) diff --git a/mygpo/maintenance/merge.py b/mygpo/maintenance/merge.py index 826cd231..5d486df9 100644 --- a/mygpo/maintenance/merge.py +++ b/mygpo/maintenance/merge.py @@ -2,7 +2,8 @@ import collections from django.db import transaction, IntegrityError from django.contrib.contenttypes.models import ContentType -from django.db.models import get_models, Model +from django.db.models import Model +from django.apps import apps from django.contrib.contenttypes.fields import GenericForeignKey from mygpo.podcasts.models import (MergedUUID, ScopedModel, OrderedModel, Slug, @@ -135,7 +136,7 @@ def merge_model_objects(primary_object, alias_objects=[], keep_old=False): # provide a similar method to the ForeignKey field for accessing the # generic related fields. generic_fields = [] - for model in get_models(): + for model in apps.get_models(): fields = filter(lambda x: isinstance(x[1], GenericForeignKey), model.__dict__.items()) for field_name, field in fields: diff --git a/mygpo/podcasts/models.py b/mygpo/podcasts/models.py index 104a32db..6726e6bf 100644 --- a/mygpo/podcasts/models.py +++ b/mygpo/podcasts/models.py @@ -129,74 +129,92 @@ class AuthorModel(models.Model): abstract = True -class UrlsMixin(models.Model): - """ Methods for working with URL objects """ +class MergedUUIDQuerySet(models.QuerySet): + """ QuerySet for Models inheriting from MergedUUID """ + + def get_by_any_id(self, id): + """ Find am Episode by its own ID or by a merged ID """ + # TODO: should this be done in the model? + try: + return self.get(id=id) + except self.model.DoesNotExist: + return self.get(merged_uuids__uuid=id) - urls = GenericRelation('URL', related_query_name='urls') + +class TagsMixin(models.Model): + """ Methods for working with Tag objects """ + + tags = GenericRelation('Tag', related_query_name='tags') class Meta: abstract = True - @property - def url(self): - """ The main URL of the model """ - # We could also use self.urls.first() here, but this would result in a - # different query and would render a .prefetch_related('urls') useless - # The assumption is that we will never have loads of URLS, so - # fetching all won't hurt - urls = list(self.urls.all()) - return urls[0].url if urls else None - def add_missing_urls(self, new_urls): - """ Adds missing URLS from new_urls +class ScopedModel(models.Model): + """ A model that belongs to some scope, usually for limited uniqueness - The order of existing URLs is not changed """ - existing_urls = self.urls.all() - next_order = max([-1] + [u.order for u in existing_urls]) + 1 - existing_urls = [u.url for u in existing_urls] + scope does not allow null values, because null is not equal to null in SQL. + It could therefore not be used in unique constraints. """ - for url in new_urls: - if url in existing_urls: - continue + # A slug / URL is unique within a scope; no two podcasts can have the same + # URL (scope ''), and no two episdoes of the same podcast (scope = + # podcast-ID) can have the same URL + scope = models.CharField(max_length=32, null=False, blank=True, + db_index=True) - try: - URL.objects.create(url=url, - order=next_order, - scope=self.scope, - content_object=self, - ) - next_order += 1 - except IntegrityError as ie: - err = str(ie) - logger.warn(u'Could not add URL: {0}'.format(err)) - continue + class Meta: + abstract = True - def set_url(self, url): - """ Sets the canonical URL """ + def get_default_scope(self): + """ Returns the default scope of the object """ + raise NotImplementedError('{cls} should implement get_default_scope' + .format(cls=self.__class__.__name__)) - urls = [u.url for u in self.urls.all()] - if url in urls: - urls.remove(url) - urls.insert(0, url) - self.set_urls(urls) - def set_urls(self, urls): - """ Update the object's URLS to the given list +class Slug(OrderedModel, ScopedModel): + """ Slug for any kind of Model + + Slugs are ordered, and the first slug is considered the canonical one. + See also :class:`SlugsMixin` + """ + + slug = models.SlugField(max_length=150, db_index=True) + + # see https://docs.djangoproject.com/en/1.6/ref/contrib/contenttypes/#generic-relations + content_type = models.ForeignKey(ContentType, on_delete=models.PROTECT) + object_id = models.UUIDField() + content_object = GenericForeignKey('content_type', 'object_id') + + class Meta(OrderedModel.Meta): + unique_together = ( + # a slug is unique per type; eg a podcast can have the same slug + # as an episode, but no two podcasts can have the same slug + ('slug', 'scope'), + + # slugs of an object must be ordered, so that no two slugs of one + # object have the same order key + ('content_type', 'object_id', 'order'), + ) + + index_together = [ + ('slug', 'content_type') + ] + + def __repr__(self): + return '{cls}(slug={slug}, order={order}, content_object={obj}'.format( + cls=self.__class__.__name__, + slug=self.slug, + order=self.order, + obj=self.content_object + ) - 'urls' should be a list of strings. Slugs that do not exist are - created. Existing urls that are not in the 'urls' list are - deleted. """ - urls = [utils.to_maxlength(URL, 'url', url) for url in urls] - existing = {u.url: u for u in self.urls.all()} - utils.set_ordered_entries(self, urls, existing, URL, 'url', - 'content_object') class SlugsMixin(models.Model): """ Methods for working with Slug objects """ - slugs = GenericRelation('Slug', related_query_name='slugs') + slugs = GenericRelation(Slug, related_query_name='slugs') class Meta: abstract = True @@ -272,36 +290,6 @@ class SlugsMixin(models.Model): 'content_object') -class MergedUUIDsMixin(models.Model): - """ Methods for working with MergedUUID objects """ - - merged_uuids = GenericRelation('MergedUUID', - related_query_name='merged_uuids') - - class Meta: - abstract = True - - -class MergedUUIDQuerySet(models.QuerySet): - """ QuerySet for Models inheriting from MergedUUID """ - - def get_by_any_id(self, id): - """ Find am Episode by its own ID or by a merged ID """ - # TODO: should this be done in the model? - try: - return self.get(id=id) - except self.model.DoesNotExist: - return self.get(merged_uuids__uuid=id) - - -class TagsMixin(models.Model): - """ Methods for working with Tag objects """ - - tags = GenericRelation('Tag', related_query_name='tags') - - class Meta: - abstract = True - class PodcastGroup(UUIDModel, TitleModel, SlugsMixin): """ Groups multiple podcasts together """ @@ -413,6 +401,130 @@ class PodcastManager(GenericManager): ) +class URL(OrderedModel, ScopedModel): + """ Podcasts and Episodes can have multiple URLs + + URLs are ordered, and the first slug is considered the canonical one """ + + url = models.URLField(max_length=2048) + + # see https://docs.djangoproject.com/en/1.6/ref/contrib/contenttypes/#generic-relations + content_type = models.ForeignKey(ContentType, on_delete=models.PROTECT) + object_id = models.UUIDField() + content_object = GenericForeignKey('content_type', 'object_id') + + class Meta(OrderedModel.Meta): + unique_together = ( + # a URL is unique per scope + ('url', 'scope'), + + # URLs of an object must be ordered, so that no two slugs of one + # object have the same order key + ('content_type', 'object_id', 'order'), + ) + + verbose_name = 'URL' + verbose_name_plural = 'URLs' + + def get_default_scope(self): + return self.content_object.scope + + + +class UrlsMixin(models.Model): + """ Methods for working with URL objects """ + + urls = GenericRelation(URL, related_query_name='urls') + + class Meta: + abstract = True + + @property + def url(self): + """ The main URL of the model """ + # We could also use self.urls.first() here, but this would result in a + # different query and would render a .prefetch_related('urls') useless + # The assumption is that we will never have loads of URLS, so + # fetching all won't hurt + urls = list(self.urls.all()) + return urls[0].url if urls else None + + def add_missing_urls(self, new_urls): + """ Adds missing URLS from new_urls + + The order of existing URLs is not changed """ + existing_urls = self.urls.all() + next_order = max([-1] + [u.order for u in existing_urls]) + 1 + existing_urls = [u.url for u in existing_urls] + + for url in new_urls: + if url in existing_urls: + continue + + try: + URL.objects.create(url=url, + order=next_order, + scope=self.scope, + content_object=self, + ) + next_order += 1 + except IntegrityError as ie: + err = str(ie) + logger.warn(u'Could not add URL: {0}'.format(err)) + continue + + def set_url(self, url): + """ Sets the canonical URL """ + + urls = [u.url for u in self.urls.all()] + if url in urls: + urls.remove(url) + + urls.insert(0, url) + self.set_urls(urls) + + def set_urls(self, urls): + """ Update the object's URLS to the given list + + 'urls' should be a list of strings. Slugs that do not exist are + created. Existing urls that are not in the 'urls' list are + deleted. """ + urls = [utils.to_maxlength(URL, 'url', url) for url in urls] + existing = {u.url: u for u in self.urls.all()} + utils.set_ordered_entries(self, urls, existing, URL, 'url', + 'content_object') + + +class MergedUUID(models.Model): + """ If objects are merged their UUIDs are stored for later reference + + see also :class:`MergedUUIDsMixin` + """ + + uuid = models.UUIDField(unique=True) + + # see https://docs.djangoproject.com/en/1.6/ref/contrib/contenttypes/#generic-relations + content_type = models.ForeignKey(ContentType, on_delete=models.PROTECT) + object_id = models.UUIDField() + content_object = GenericForeignKey('content_type', 'object_id') + + class Meta: + verbose_name = 'Merged UUID' + verbose_name_plural = 'Merged UUIDs' + + +class MergedUUIDsMixin(models.Model): + """ Methods for working with MergedUUID objects """ + + merged_uuids = GenericRelation(MergedUUID, + related_query_name='merged_uuids') + + class Meta: + abstract = True + + + + class Podcast(UUIDModel, TitleModel, DescriptionModel, LinkModel, LanguageModel, LastUpdateModel, UpdateInfoModel, LicenseModel, FlattrModel, ContentTypesModel, MergedIdsModel, OutdatedModel, @@ -681,56 +793,6 @@ class Episode(UUIDModel, TitleModel, DescriptionModel, LinkModel, return int(match.group(1)) -class ScopedModel(models.Model): - """ A model that belongs to some scope, usually for limited uniqueness - - scope does not allow null values, because null is not equal to null in SQL. - It could therefore not be used in unique constraints. """ - - # A slug / URL is unique within a scope; no two podcasts can have the same - # URL (scope ''), and no two episdoes of the same podcast (scope = - # podcast-ID) can have the same URL - scope = models.CharField(max_length=32, null=False, blank=True, - db_index=True) - - class Meta: - abstract = True - - def get_default_scope(self): - """ Returns the default scope of the object """ - raise NotImplementedError('{cls} should implement get_default_scope' - .format(cls=self.__class__.__name__)) - - -class URL(OrderedModel, ScopedModel): - """ Podcasts and Episodes can have multiple URLs - - URLs are ordered, and the first slug is considered the canonical one """ - - url = models.URLField(max_length=2048) - - # see https://docs.djangoproject.com/en/1.6/ref/contrib/contenttypes/#generic-relations - content_type = models.ForeignKey(ContentType, on_delete=models.PROTECT) - object_id = models.UUIDField() - content_object = GenericForeignKey('content_type', 'object_id') - - class Meta(OrderedModel.Meta): - unique_together = ( - # a URL is unique per scope - ('url', 'scope'), - - # URLs of an object must be ordered, so that no two slugs of one - # object have the same order key - ('content_type', 'object_id', 'order'), - ) - - verbose_name = 'URL' - verbose_name_plural = 'URLs' - - def get_default_scope(self): - return self.content_object.scope - - class Tag(models.Model): """ Tags any kind of Model @@ -769,57 +831,4 @@ class Tag(models.Model): ) -class Slug(OrderedModel, ScopedModel): - """ Slug for any kind of Model - - Slugs are ordered, and the first slug is considered the canonical one. - See also :class:`SlugsMixin` - """ - - slug = models.SlugField(max_length=150, db_index=True) - # see https://docs.djangoproject.com/en/1.6/ref/contrib/contenttypes/#generic-relations - content_type = models.ForeignKey(ContentType, on_delete=models.PROTECT) - object_id = models.UUIDField() - content_object = GenericForeignKey('content_type', 'object_id') - - class Meta(OrderedModel.Meta): - unique_together = ( - # a slug is unique per type; eg a podcast can have the same slug - # as an episode, but no two podcasts can have the same slug - ('slug', 'scope'), - - # slugs of an object must be ordered, so that no two slugs of one - # object have the same order key - ('content_type', 'object_id', 'order'), - ) - - index_together = [ - ('slug', 'content_type') - ] - - def __repr__(self): - return '{cls}(slug={slug}, order={order}, content_object={obj}'.format( - cls=self.__class__.__name__, - slug=self.slug, - order=self.order, - obj=self.content_object - ) - - -class MergedUUID(models.Model): - """ If objects are merged their UUIDs are stored for later reference - - see also :class:`MergedUUIDsMixin` - """ - - uuid = models.UUIDField(unique=True) - - # see https://docs.djangoproject.com/en/1.6/ref/contrib/contenttypes/#generic-relations - content_type = models.ForeignKey(ContentType, on_delete=models.PROTECT) - object_id = models.UUIDField() - content_object = GenericForeignKey('content_type', 'object_id') - - class Meta: - verbose_name = 'Merged UUID' - verbose_name_plural = 'Merged UUIDs' diff --git a/mygpo/usersettings/models.py b/mygpo/usersettings/models.py index 14ebb1fc..a020bb7d 100644 --- a/mygpo/usersettings/models.py +++ b/mygpo/usersettings/models.py @@ -3,8 +3,7 @@ import json from django.db import models from django.conf import settings from django.contrib.contenttypes.models import ContentType -from django.contrib.contenttypes.fields import (GenericRelation, - GenericForeignKey) +from django.contrib.contenttypes.fields import GenericForeignKey from mygpo.users.settings import PUBLIC_SUB_PODCAST from mygpo.podcasts.models import Podcast diff --git a/mygpo/votes/models.py b/mygpo/votes/models.py index 29663097..c7871d17 100644 --- a/mygpo/votes/models.py +++ b/mygpo/votes/models.py @@ -29,7 +29,7 @@ class Vote(UpdateInfoModel): class VoteMixin(models.Model): - votes = GenericRelation('Vote', related_query_name='votes') + votes = GenericRelation(Vote, related_query_name='votes') class Meta: abstract = True diff --git a/requirements.txt b/requirements.txt index 8f96bf83..78866024 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ Babel==2.1.1 Pillow==3.0.0 -Django==1.8.7 +Django==1.9.1 celery==3.1.19 dj-database-url==0.3.0 django-redis-sessions==0.5.0 -- 2.11.4.GIT