Commit e56a78a7 by paul

python_2_unicode_compatible decorateur

parent 0d10395f
...@@ -69,7 +69,7 @@ class URLPathAdmin(MPTTModelAdmin): ...@@ -69,7 +69,7 @@ class URLPathAdmin(MPTTModelAdmin):
list_filter = ('site', 'articles__article__current_revision__deleted', list_filter = ('site', 'articles__article__current_revision__deleted',
'articles__article__created', 'articles__article__created',
'articles__article__modified') 'articles__article__modified')
list_display = ('__unicode__', 'article', 'get_created') list_display = ('__str__', 'article', 'get_created')
def get_created(self, instance): def get_created(self, instance):
return instance.article.created return instance.article.created
......
...@@ -7,6 +7,7 @@ from django.contrib.auth.models import Group ...@@ -7,6 +7,7 @@ from django.contrib.auth.models import Group
from django.core.cache import cache from django.core.cache import cache
from django.db import models from django.db import models
from django.db.models.signals import post_save, pre_delete from django.db.models.signals import post_save, pre_delete
from django.utils.encoding import python_2_unicode_compatible
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
...@@ -17,6 +18,7 @@ from wiki import managers ...@@ -17,6 +18,7 @@ from wiki import managers
from mptt.models import MPTTModel from mptt.models import MPTTModel
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
@python_2_unicode_compatible
class Article(models.Model): class Article(models.Model):
objects = managers.ArticleManager() objects = managers.ArticleManager()
...@@ -139,7 +141,7 @@ class Article(models.Model): ...@@ -139,7 +141,7 @@ class Article(models.Model):
def get_for_object(cls, obj): def get_for_object(cls, obj):
return ArticleForObject.objects.get(object_id=obj.id, content_type=ContentType.objects.get_for_model(obj)).article return ArticleForObject.objects.get(object_id=obj.id, content_type=ContentType.objects.get_for_model(obj)).article
def __unicode__(self): def __str__(self):
if self.current_revision: if self.current_revision:
return self.current_revision.title return self.current_revision.title
obj_name = _('Article without content (%(id)d)') % {'id': self.id} obj_name = _('Article without content (%(id)d)') % {'id': self.id}
...@@ -184,17 +186,17 @@ class Article(models.Model): ...@@ -184,17 +186,17 @@ class Article(models.Model):
else: else:
return reverse('wiki:get', kwargs={'article_id': self.id}) return reverse('wiki:get', kwargs={'article_id': self.id})
class ArticleForObject(models.Model): class ArticleForObject(models.Model):
objects = managers.ArticleFkManager() objects = managers.ArticleFkManager()
article = models.ForeignKey('Article', on_delete=models.CASCADE) article = models.ForeignKey('Article', on_delete=models.CASCADE)
# Same as django.contrib.comments # Same as django.contrib.comments
content_type = models.ForeignKey(ContentType, content_type = models.ForeignKey(ContentType,
verbose_name=_('content type'), verbose_name=_('content type'),
related_name="content_type_set_for_%(class)s") related_name="content_type_set_for_%(class)s")
object_id = models.PositiveIntegerField(_('object ID')) object_id = models.PositiveIntegerField(_('object ID'))
content_object = generic.GenericForeignKey("content_type", "object_id") content_object = generic.GenericForeignKey("content_type", "object_id")
is_mptt = models.BooleanField(default=False, editable=False) is_mptt = models.BooleanField(default=False, editable=False)
...@@ -206,6 +208,7 @@ class ArticleForObject(models.Model): ...@@ -206,6 +208,7 @@ class ArticleForObject(models.Model):
# Do not allow several objects # Do not allow several objects
unique_together = ('content_type', 'object_id') unique_together = ('content_type', 'object_id')
class BaseRevisionMixin(models.Model): class BaseRevisionMixin(models.Model):
"""This is an abstract model used as a mixin: Do not override any of the """This is an abstract model used as a mixin: Do not override any of the
core model methods but respect the inheritor's freedom to do so itself.""" core model methods but respect the inheritor's freedom to do so itself."""
...@@ -215,10 +218,10 @@ class BaseRevisionMixin(models.Model): ...@@ -215,10 +218,10 @@ class BaseRevisionMixin(models.Model):
user_message = models.TextField(blank=True,) user_message = models.TextField(blank=True,)
automatic_log = models.TextField(blank=True, editable=False,) automatic_log = models.TextField(blank=True, editable=False,)
ip_address = models.IPAddressField(_('IP address'), blank=True, null=True, editable=False) ip_address = models.IPAddressField(_('IP address'), blank=True, null=True, editable=False)
user = models.ForeignKey(compat.USER_MODEL, verbose_name=_('user'), user = models.ForeignKey(compat.USER_MODEL, verbose_name=_('user'),
blank=True, null=True, blank=True, null=True,
on_delete=models.SET_NULL) on_delete=models.SET_NULL)
modified = models.DateTimeField(auto_now=True) modified = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
...@@ -234,7 +237,7 @@ class BaseRevisionMixin(models.Model): ...@@ -234,7 +237,7 @@ class BaseRevisionMixin(models.Model):
verbose_name=_('deleted'), verbose_name=_('deleted'),
default=False, default=False,
) )
locked = models.BooleanField( locked = models.BooleanField(
verbose_name=_('locked'), verbose_name=_('locked'),
default=False, default=False,
) )
...@@ -249,7 +252,9 @@ class BaseRevisionMixin(models.Model): ...@@ -249,7 +252,9 @@ class BaseRevisionMixin(models.Model):
class Meta: class Meta:
abstract = True abstract = True
@python_2_unicode_compatible
class ArticleRevision(BaseRevisionMixin, models.Model): class ArticleRevision(BaseRevisionMixin, models.Model):
"""This is where main revision data is stored. To make it easier to """This is where main revision data is stored. To make it easier to
copy, do NEVER create m2m relationships.""" copy, do NEVER create m2m relationships."""
...@@ -273,7 +278,7 @@ class ArticleRevision(BaseRevisionMixin, models.Model): ...@@ -273,7 +278,7 @@ class ArticleRevision(BaseRevisionMixin, models.Model):
# help_text=_('If set, the article will redirect to the contents of another article.'), # help_text=_('If set, the article will redirect to the contents of another article.'),
# related_name='redirect_set') # related_name='redirect_set')
def __unicode__(self): def __str__(self):
return "%s (%d)" % (self.title, self.revision_number) return "%s (%d)" % (self.title, self.revision_number)
def inherit_predecessor(self, article): def inherit_predecessor(self, article):
...@@ -328,10 +333,12 @@ def _clear_ancestor_cache(article): ...@@ -328,10 +333,12 @@ def _clear_ancestor_cache(article):
for ancestor in article.ancestor_objects(): for ancestor in article.ancestor_objects():
ancestor.article.clear_cache() ancestor.article.clear_cache()
def on_article_save_clear_cache(instance, **kwargs): def on_article_save_clear_cache(instance, **kwargs):
on_article_delete_clear_cache(instance, **kwargs) on_article_delete_clear_cache(instance, **kwargs)
post_save.connect(on_article_save_clear_cache, Article) post_save.connect(on_article_save_clear_cache, Article)
def on_article_delete_clear_cache(instance, **kwargs): def on_article_delete_clear_cache(instance, **kwargs):
_clear_ancestor_cache(instance) _clear_ancestor_cache(instance)
instance.clear_cache() instance.clear_cache()
......
...@@ -29,6 +29,7 @@ There are three kinds of plugin base models: ...@@ -29,6 +29,7 @@ There are three kinds of plugin base models:
from .article import ArticleRevision, BaseRevisionMixin from .article import ArticleRevision, BaseRevisionMixin
from wiki.conf import settings from wiki.conf import settings
class ArticlePlugin(models.Model): class ArticlePlugin(models.Model):
"""This is the mother of all plugins. Extending from it means a deletion """This is the mother of all plugins. Extending from it means a deletion
of an article will CASCADE to your plugin, and the database will be kept of an article will CASCADE to your plugin, and the database will be kept
...@@ -45,10 +46,13 @@ class ArticlePlugin(models.Model): ...@@ -45,10 +46,13 @@ class ArticlePlugin(models.Model):
# Permission methods - you should override these, if they don't fit your logic. # Permission methods - you should override these, if they don't fit your logic.
def can_read(self, user): def can_read(self, user):
return self.article.can_read(user) return self.article.can_read(user)
def can_write(self, user): def can_write(self, user):
return self.article.can_write(user) return self.article.can_write(user)
def can_delete(self, user): def can_delete(self, user):
return self.article.can_delete(user) return self.article.can_delete(user)
def can_moderate(self, user): def can_moderate(self, user):
return self.article.can_moderate(user) return self.article.can_moderate(user)
...@@ -60,7 +64,8 @@ class ArticlePlugin(models.Model): ...@@ -60,7 +64,8 @@ class ArticlePlugin(models.Model):
# Override this setting with app_label = '' in your extended model # Override this setting with app_label = '' in your extended model
# if it lives outside the wiki app. # if it lives outside the wiki app.
app_label = settings.APP_LABEL app_label = settings.APP_LABEL
class ReusablePlugin(ArticlePlugin): class ReusablePlugin(ArticlePlugin):
"""Extend from this model if you have a plugin that may be related to many """Extend from this model if you have a plugin that may be related to many
articles. Please note that the ArticlePlugin.article ForeignKey STAYS! This articles. Please note that the ArticlePlugin.article ForeignKey STAYS! This
...@@ -75,9 +80,9 @@ class ReusablePlugin(ArticlePlugin): ...@@ -75,9 +80,9 @@ class ReusablePlugin(ArticlePlugin):
""" """
# The article on which the plugin was originally created. # The article on which the plugin was originally created.
# Used to apply permissions. # Used to apply permissions.
ArticlePlugin.article.on_delete=models.SET_NULL ArticlePlugin.article.on_delete = models.SET_NULL
ArticlePlugin.article.verbose_name=_('original article') ArticlePlugin.article.verbose_name = _('original article')
ArticlePlugin.article.help_text=_('Permissions are inherited from this article') ArticlePlugin.article.help_text = _('Permissions are inherited from this article')
ArticlePlugin.article.null = True ArticlePlugin.article.null = True
ArticlePlugin.article.blank = True ArticlePlugin.article.blank = True
...@@ -87,10 +92,13 @@ class ReusablePlugin(ArticlePlugin): ...@@ -87,10 +92,13 @@ class ReusablePlugin(ArticlePlugin):
# before handling permissions.... # before handling permissions....
def can_read(self, user): def can_read(self, user):
return self.article.can_read(user) if self.article else False return self.article.can_read(user) if self.article else False
def can_write(self, user): def can_write(self, user):
return self.article.can_write(user) if self.article else False return self.article.can_write(user) if self.article else False
def can_delete(self, user): def can_delete(self, user):
return self.article.can_delete(user) if self.article else False return self.article.can_delete(user) if self.article else False
def can_moderate(self, user): def can_moderate(self, user):
return self.article.can_moderate(user) if self.article else False return self.article.can_moderate(user) if self.article else False
...@@ -109,8 +117,10 @@ class ReusablePlugin(ArticlePlugin): ...@@ -109,8 +117,10 @@ class ReusablePlugin(ArticlePlugin):
# if it lives outside the wiki app. # if it lives outside the wiki app.
app_label = settings.APP_LABEL app_label = settings.APP_LABEL
class SimplePluginCreateError(Exception): pass class SimplePluginCreateError(Exception): pass
class SimplePlugin(ArticlePlugin): class SimplePlugin(ArticlePlugin):
""" """
Inherit from this model and make sure to specify an article when Inherit from this model and make sure to specify an article when
...@@ -162,6 +172,7 @@ class SimplePlugin(ArticlePlugin): ...@@ -162,6 +172,7 @@ class SimplePlugin(ArticlePlugin):
# if it lives outside the wiki app. # if it lives outside the wiki app.
app_label = settings.APP_LABEL app_label = settings.APP_LABEL
class RevisionPlugin(ArticlePlugin): class RevisionPlugin(ArticlePlugin):
""" """
If you want your plugin to maintain revisions, extend from this one, If you want your plugin to maintain revisions, extend from this one,
...@@ -253,6 +264,7 @@ class RevisionPluginRevision(BaseRevisionMixin, models.Model): ...@@ -253,6 +264,7 @@ class RevisionPluginRevision(BaseRevisionMixin, models.Model):
# It's my art, when I disguise my body in the shape of a plane. # It's my art, when I disguise my body in the shape of a plane.
# (Shellac, 1993) # (Shellac, 1993)
def update_simple_plugins(**kwargs): def update_simple_plugins(**kwargs):
"""Every time a new article revision is created, we update all active """Every time a new article revision is created, we update all active
plugins to match this article revision""" plugins to match this article revision"""
...@@ -262,15 +274,18 @@ def update_simple_plugins(**kwargs): ...@@ -262,15 +274,18 @@ def update_simple_plugins(**kwargs):
# TODO: This was breaking things. SimplePlugin doesn't have a revision? # TODO: This was breaking things. SimplePlugin doesn't have a revision?
p_revisions.update(article_revision=instance) p_revisions.update(article_revision=instance)
def on_article_plugin_post_save(**kwargs): def on_article_plugin_post_save(**kwargs):
articleplugin = kwargs['instance'] articleplugin = kwargs['instance']
articleplugin.article.clear_cache() articleplugin.article.clear_cache()
def on_reusable_plugin_post_save(**kwargs): def on_reusable_plugin_post_save(**kwargs):
reusableplugin = kwargs['instance'] reusableplugin = kwargs['instance']
for article in reusableplugin.articles.all(): for article in reusableplugin.articles.all():
article.clear_cache() article.clear_cache()
def on_revision_plugin_revision_post_save(**kwargs): def on_revision_plugin_revision_post_save(**kwargs):
revision = kwargs['instance'] revision = kwargs['instance']
revision.plugin.article.clear_cache() revision.plugin.article.clear_cache()
......
...@@ -14,6 +14,8 @@ from django.db import models, transaction ...@@ -14,6 +14,8 @@ from django.db import models, transaction
from six.moves import filter from six.moves import filter
#Django 1.6 transaction API, required for 1.8+ #Django 1.6 transaction API, required for 1.8+
from django.utils.encoding import python_2_unicode_compatible
try: try:
notrans=transaction.non_atomic_requests notrans=transaction.non_atomic_requests
except: except:
...@@ -33,6 +35,8 @@ from wiki.models.article import ArticleRevision, ArticleForObject, Article ...@@ -33,6 +35,8 @@ from wiki.models.article import ArticleRevision, ArticleForObject, Article
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@python_2_unicode_compatible
class URLPath(MPTTModel): class URLPath(MPTTModel):
""" """
Strategy: Very few fields go here, as most has to be managed through an Strategy: Very few fields go here, as most has to be managed through an
...@@ -155,7 +159,7 @@ class URLPath(MPTTModel): ...@@ -155,7 +159,7 @@ class URLPath(MPTTModel):
class MPTTMeta: class MPTTMeta:
pass pass
def __unicode__(self): def __str__(self):
path = self.path path = self.path
return path if path else ugettext("(root)") return path if path else ugettext("(root)")
...@@ -260,6 +264,7 @@ class URLPath(MPTTModel): ...@@ -260,6 +264,7 @@ class URLPath(MPTTModel):
# Just get this once # Just get this once
urlpath_content_type = None urlpath_content_type = None
def on_article_relation_save(**kwargs): def on_article_relation_save(**kwargs):
global urlpath_content_type global urlpath_content_type
instance = kwargs['instance'] instance = kwargs['instance']
...@@ -270,12 +275,14 @@ def on_article_relation_save(**kwargs): ...@@ -270,12 +275,14 @@ def on_article_relation_save(**kwargs):
post_save.connect(on_article_relation_save, ArticleForObject) post_save.connect(on_article_relation_save, ArticleForObject)
class Namespace: class Namespace:
# An instance of Namespace simulates "nonlocal variable_name" declaration # An instance of Namespace simulates "nonlocal variable_name" declaration
# in any nested function, that is possible in Python 3. It allows assigning # in any nested function, that is possible in Python 3. It allows assigning
# to non local variable without rebinding it local. See PEP 3104. # to non local variable without rebinding it local. See PEP 3104.
pass pass
def on_article_delete(instance, *args, **kwargs): def on_article_delete(instance, *args, **kwargs):
# If an article is deleted, then throw out its URLPaths # If an article is deleted, then throw out its URLPaths
# But move all descendants to a lost-and-found node. # But move all descendants to a lost-and-found node.
...@@ -286,18 +293,19 @@ def on_article_delete(instance, *args, **kwargs): ...@@ -286,18 +293,19 @@ def on_article_delete(instance, *args, **kwargs):
# that the lost-and-found article can be deleted without being recreated! # that the lost-and-found article can be deleted without being recreated!
ns = Namespace() # nonlocal namespace backported to Python 2.x ns = Namespace() # nonlocal namespace backported to Python 2.x
ns.lost_and_found = None ns.lost_and_found = None
def get_lost_and_found(): def get_lost_and_found():
if ns.lost_and_found: if ns.lost_and_found:
return ns.lost_and_found return ns.lost_and_found
try: try:
ns.lost_and_found = URLPath.objects.get(slug=settings.LOST_AND_FOUND_SLUG, ns.lost_and_found = URLPath.objects.get(slug=settings.LOST_AND_FOUND_SLUG,
parent=URLPath.root(), parent=URLPath.root(),
site=site) site=site)
except URLPath.DoesNotExist: except URLPath.DoesNotExist:
article = Article(group_read = True, article = Article(group_read=True,
group_write = False, group_write=False,
other_read = False, other_read=False,
other_write = False) other_write=False)
article.add_revision(ArticleRevision( article.add_revision(ArticleRevision(
content=_('Articles who lost their parents\n' content=_('Articles who lost their parents\n'
'===============================\n\n' '===============================\n\n'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment