Commit ab7dc9a0 by benjaoming

Adding migrations for URLPath.article cache field

Adding dependency on sorl-thumbnail
parent 3e7ddf6a
...@@ -115,6 +115,7 @@ So far the dependencies are: ...@@ -115,6 +115,7 @@ So far the dependencies are:
* [Markdown>=2.2.0](https://github.com/waylan/Python-Markdown) * [Markdown>=2.2.0](https://github.com/waylan/Python-Markdown)
* [django-mptt>=0.5](https://github.com/django-mptt/django-mptt) * [django-mptt>=0.5](https://github.com/django-mptt/django-mptt)
* [django-sekizai](https://github.com/ojii/django-sekizai/) * [django-sekizai](https://github.com/ojii/django-sekizai/)
* [sorl-thumbnail](https://github.com/sorl/sorl-thumbnail)
Development Development
------------ ------------
......
...@@ -3,3 +3,4 @@ South<0.8 ...@@ -3,3 +3,4 @@ South<0.8
Markdown<2.3.0 Markdown<2.3.0
django-sekizai<0.7 django-sekizai<0.7
django-mptt>=0.5 django-mptt>=0.5
sorl-thumbnail
\ No newline at end of file
...@@ -6,6 +6,17 @@ that you have checked out the root of the Git repository. ...@@ -6,6 +6,17 @@ that you have checked out the root of the Git repository.
It comes with a prepopulated SQLite database. It comes with a prepopulated SQLite database.
Setup
-----
You should link your wiki and django_notify folders like so
ln -s ../wiki .
ln -s ../django_notify .
Login
-----
Django admin: Django admin:
Username: admin Username: admin
......
...@@ -104,6 +104,7 @@ INSTALLED_APPS = ( ...@@ -104,6 +104,7 @@ INSTALLED_APPS = (
'south', 'south',
'sekizai', 'sekizai',
'django_notify', 'django_notify',
'sorl.thumbnail',
'wiki', 'wiki',
'wiki.plugins.images', 'wiki.plugins.images',
'wiki.plugins.attachments', 'wiki.plugins.attachments',
......
...@@ -3,10 +3,12 @@ from django.conf import settings as django_settings ...@@ -3,10 +3,12 @@ from django.conf import settings as django_settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.shortcuts import redirect, get_object_or_404, render_to_response from django.shortcuts import redirect, get_object_or_404, render_to_response
from django.template.context import RequestContext from django.template.context import RequestContext
from django.http import HttpResponse, HttpResponseNotFound from django.http import HttpResponse, HttpResponseNotFound,\
HttpResponseForbidden
from django.utils import simplejson as json from django.utils import simplejson as json
from wiki.core.exceptions import NoRootURL from wiki.core.exceptions import NoRootURL
from django.template.loader import render_to_string
def json_view(func): def json_view(func):
def wrap(request, *args, **kwargs): def wrap(request, *args, **kwargs):
...@@ -79,7 +81,7 @@ def get_article(func=None, can_read=True, can_write=False, deleted_contents=Fals ...@@ -79,7 +81,7 @@ def get_article(func=None, can_read=True, can_write=False, deleted_contents=Fals
# fetch by article.id # fetch by article.id
if article_id: elif article_id:
article = get_object_or_404(articles, id=article_id) article = get_object_or_404(articles, id=article_id)
try: try:
urlpath = models.URLPath.objects.get(articles__article=article) urlpath = models.URLPath.objects.get(articles__article=article)
...@@ -96,14 +98,14 @@ def get_article(func=None, can_read=True, can_write=False, deleted_contents=Fals ...@@ -96,14 +98,14 @@ def get_article(func=None, can_read=True, can_write=False, deleted_contents=Fals
return redirect(django_settings.LOGIN_URL) return redirect(django_settings.LOGIN_URL)
else: else:
c = RequestContext(request, {'urlpath' : urlpath}) c = RequestContext(request, {'urlpath' : urlpath})
return render_to_response("wiki/permission_denied.html", context_instance=c) return HttpResponseForbidden(render_to_string("wiki/permission_denied.html", c))
if can_write and not article.can_write(request.user): if can_write and not article.can_write(request.user):
if request.user.is_anonymous(): if request.user.is_anonymous():
return redirect(django_settings.LOGIN_URL) return redirect(django_settings.LOGIN_URL)
else: else:
c = RequestContext(request, {'urlpath' : urlpath}) c = RequestContext(request, {'urlpath' : urlpath})
return render_to_response("wiki/permission_denied.html", context_instance=c) return HttpResponseForbidden(render_to_string("wiki/permission_denied.html", c))
# If the article has been deleted, show a special page. # If the article has been deleted, show a special page.
if not deleted_contents and article.current_revision and article.current_revision.deleted: if not deleted_contents and article.current_revision and article.current_revision.deleted:
......
...@@ -4,7 +4,7 @@ from django.contrib.sites.models import Site ...@@ -4,7 +4,7 @@ from django.contrib.sites.models import Site
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import models from django.db import models
from django.db.models.signals import pre_delete from django.db.models.signals import pre_delete, post_save
from django.utils.translation import ugettext_lazy as _, ugettext from django.utils.translation import ugettext_lazy as _, ugettext
from mptt.fields import TreeForeignKey from mptt.fields import TreeForeignKey
...@@ -13,6 +13,7 @@ from mptt.models import MPTTModel ...@@ -13,6 +13,7 @@ from mptt.models import MPTTModel
from wiki.conf import settings from wiki.conf import settings
from wiki.core.exceptions import NoRootURL, MultipleRootURLs from wiki.core.exceptions import NoRootURL, MultipleRootURLs
from wiki.models.article import ArticleRevision, ArticleForObject, Article from wiki.models.article import ArticleRevision, ArticleForObject, Article
from django.contrib.contenttypes.models import ContentType
class URLPath(MPTTModel): class URLPath(MPTTModel):
""" """
...@@ -25,6 +26,11 @@ class URLPath(MPTTModel): ...@@ -25,6 +26,11 @@ class URLPath(MPTTModel):
INHERIT_PERMISSIONS = True INHERIT_PERMISSIONS = True
articles = generic.GenericRelation(ArticleForObject) articles = generic.GenericRelation(ArticleForObject)
# Do NOT modify this field - it is updated with signals whenever ArticleForObject is changed.
article = models.ForeignKey(Article, on_delete=models.CASCADE, editable=False,
verbose_name=_(u'Cache lookup value for articles'))
slug = models.SlugField(verbose_name=_(u'slug'), null=True, blank=True) slug = models.SlugField(verbose_name=_(u'slug'), null=True, blank=True)
site = models.ForeignKey(Site) site = models.ForeignKey(Site)
parent = TreeForeignKey('self', null=True, blank=True, related_name='children') parent = TreeForeignKey('self', null=True, blank=True, related_name='children')
...@@ -85,6 +91,11 @@ class URLPath(MPTTModel): ...@@ -85,6 +91,11 @@ class URLPath(MPTTModel):
Strategy: Don't handle all kinds of weird cases. Be strict. Strategy: Don't handle all kinds of weird cases. Be strict.
Accepts paths both starting with and without '/' Accepts paths both starting with and without '/'
""" """
# TODO: Save paths directly in the model for constant time lookups?
# Or: Save the parents in a lazy property because the parents are
# always fetched anyways so it's fine to fetch them here.
path = path.lstrip("/") path = path.lstrip("/")
path = path.rstrip("/") path = path.rstrip("/")
...@@ -127,25 +138,28 @@ class URLPath(MPTTModel): ...@@ -127,25 +138,28 @@ class URLPath(MPTTModel):
"""Utility function: """Utility function:
Create a new urlpath with an article and a new revision for the article""" Create a new urlpath with an article and a new revision for the article"""
if not site: site = Site.objects.get_current() if not site: site = Site.objects.get_current()
newpath = cls.objects.create(site=site, parent=parent, slug=slug)
article = Article(**article_kwargs) article = Article(**article_kwargs)
article.add_revision(ArticleRevision(title=title, **kwargs), article.add_revision(ArticleRevision(title=title, **kwargs),
save=True) save=True)
article.save()
newpath = cls.objects.create(site=site, parent=parent, slug=slug, article=article)
article.add_object_relation(newpath) article.add_object_relation(newpath)
return newpath return newpath
@property
def article(self):
try:
return self.articles.all()[0].article
except IndexError:
return None
###################################################### ######################################################
# SIGNAL HANDLERS # SIGNAL HANDLERS
###################################################### ######################################################
# Just get this once
urlpath_content_type = ContentType.objects.get_for_model(URLPath)
def on_article_relation_save(instance, *args, **kwargs):
if instance.content_type == urlpath_content_type:
URLPath.objects.filter(id=instance.object_id).update(article=instance.article)
post_save.connect(on_article_relation_save, ArticleForObject)
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.
...@@ -168,12 +182,13 @@ def on_article_delete(instance, *args, **kwargs): ...@@ -168,12 +182,13 @@ def on_article_delete(instance, *args, **kwargs):
content=_(u'Articles who lost their parents' content=_(u'Articles who lost their parents'
'==============================='), '==============================='),
title=_(u"Lost and found"))) title=_(u"Lost and found")))
for urlpath in URLPath.objects.filter(articles__article=instance, site=site): for urlpath in URLPath.objects.filter(articles__article=instance, site=site):
# Delete the children # Delete the children
for child in urlpath.get_children(): for child in urlpath.get_children():
child.move_to(lost_and_found) child.move_to(lost_and_found)
# ...and finally delete the path itself # ...and finally delete the path itself
# TODO: This should be unnecessary because of URLPath.article(...ondelete=models.CASCADE)
urlpath.delete() urlpath.delete()
pre_delete.connect(on_article_delete, Article) pre_delete.connect(on_article_delete, Article)
...@@ -61,7 +61,6 @@ class Migration(SchemaMigration): ...@@ -61,7 +61,6 @@ class Migration(SchemaMigration):
}, },
'images.image': { 'images.image': {
'Meta': {'object_name': 'Image', '_ormbases': ['wiki.RevisionPlugin']}, 'Meta': {'object_name': 'Image', '_ormbases': ['wiki.RevisionPlugin']},
'caption': ('django.db.models.fields.CharField', [], {'max_length': '2056', 'null': 'True', 'blank': 'True'}),
'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
'revisionplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.RevisionPlugin']", 'unique': 'True', 'primary_key': 'True'}) 'revisionplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.RevisionPlugin']", 'unique': 'True', 'primary_key': 'True'})
}, },
......
from django.conf import settings as django_settings
from django.core.exceptions import ImproperlyConfigured
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
...@@ -5,9 +7,13 @@ import settings ...@@ -5,9 +7,13 @@ import settings
from wiki.models.pluginbase import RevisionPlugin from wiki.models.pluginbase import RevisionPlugin
if not "sorl.thumbnail" in django_settings.INSTALLED_APPS:
raise ImproperlyConfigured('wiki.plugins.images: needs sorl.thumbnail in INSTALLED_APPS')
class Image(RevisionPlugin): class Image(RevisionPlugin):
image = models.ImageField(upload_to=settings.IMAGE_PATH) image = models.ImageField(upload_to=settings.IMAGE_PATH,
max_length=2000)
def get_filename(self): def get_filename(self):
if self.image: if self.image:
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
{% endfor %} {% endfor %}
</table> </table>
{% if article|can_write:user %}
<hr /> <hr />
<h4>{% trans "Add new image" %}</h4> <h4>{% trans "Add new image" %}</h4>
...@@ -55,3 +56,5 @@ ...@@ -55,3 +56,5 @@
{% trans "Add image" %} {% trans "Add image" %}
</button> </button>
</p> </p>
{% endif %}
from wiki.views.mixins import ArticleMixin
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from wiki.decorators import get_article from wiki.decorators import get_article
from wiki.views.mixins import ArticleMixin
class ImageView(ArticleMixin, TemplateView): class ImageView(ArticleMixin, TemplateView):
......
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