From 33e9a24b9a208b4cc2153f539ea4f3bee1ea5918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andre=CC=81s=20Rocha?= Date: Sat, 25 Aug 2012 09:23:51 -0400 Subject: [PATCH 01/80] Important fix! Remove HTML tags from Markdown code. --- wiki/core/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/wiki/core/__init__.py b/wiki/core/__init__.py index 64bc48c98..e0bb34536 100644 --- a/wiki/core/__init__.py +++ b/wiki/core/__init__.py @@ -3,6 +3,7 @@ class ArticleMarkdown(markdown.Markdown): def __init__(self, article, *args, **kwargs): + kwargs['safe_mode'] = "remove" markdown.Markdown.__init__(self, *args, **kwargs) self.article = article From ac906abebd60bcd27dad9f7a7df70d8b273777fa Mon Sep 17 00:00:00 2001 From: benjaoming Date: Tue, 23 Apr 2013 18:19:13 +0200 Subject: [PATCH 02/80] Make the urlize parser more strict --- wiki/plugins/links/mdx/urlize.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/wiki/plugins/links/mdx/urlize.py b/wiki/plugins/links/mdx/urlize.py index 07e55c230..2d545c899 100644 --- a/wiki/plugins/links/mdx/urlize.py +++ b/wiki/plugins/links/mdx/urlize.py @@ -40,20 +40,31 @@ """ import markdown - -# Global Vars -URLIZE_RE = '(%s)' % '|'.join([ - r'<(?:f|ht)tps?://[^>\'"]*>', - r'\b(?:f|ht)tps?://[^)<>\s\'"]+[^.,)<>\s\'"]', - r'\bwww\.[^)<>\s]+[^.,)<>\s\'"]', - r'[^(<\s\'"]+\.(?:com|net|org)\b', -]) +import re + +# Taken from Django trunk 2f121dfe635b3f497fe1fe03bc8eb97cdf5083b3 +# https://github.com/django/django/blob/master/django/core/validators.py#L47 +URLIZE_RE = ( + r'((?:(?:http|ftp)s?://|www\.)' # http:// or https:// + r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain... + r'localhost|' # localhost... + r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4 + r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6 + r'(?::\d+)?' # optional port + r'(?:/?|[/?]\S+))' +) class UrlizePattern(markdown.inlinepatterns.Pattern): + + def __init__(self, pattern, markdown_instance=None): + markdown.inlinepatterns.Pattern.__init__(self, pattern, markdown_instance=markdown_instance) + self.compiled_re = re.compile("^(.*?)%s(.*?)$" % pattern, + re.DOTALL | re.UNICODE | re.IGNORECASE) + """ Return a link Element given an autolink (`http://example/com`). """ def handleMatch(self, m): url = m.group(2) - + if url.startswith('<'): url = url[1:-1] @@ -70,7 +81,6 @@ def handleMatch(self, m): span_text = markdown.util.etree.Element("span") span_text.text = markdown.util.AtomicString(" " + text) - el = markdown.util.etree.Element("a") el.set('href', url) el.set('target', '_blank') From 791026aa5dbcab1be3c1b1021b3da90a66e2ea95 Mon Sep 17 00:00:00 2001 From: Adam Palay Date: Thu, 18 Jul 2013 17:55:10 -0400 Subject: [PATCH 03/80] fix syntax error in views/article --- wiki/views/article.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wiki/views/article.py b/wiki/views/article.py index 8a66d17be..61258bd9b 100644 --- a/wiki/views/article.py +++ b/wiki/views/article.py @@ -88,7 +88,7 @@ def form_valid(self, form): # TODO: Handle individual exceptions better and give good feedback. except Exception, e: transaction.rollback() - if self.request.user.is_superuser(): + if self.request.user.is_superuser: messages.error(self.request, _(u"There was an error creating this article: %s") % str(e)) else: messages.error(self.request, _(u"There was an error creating this article.")) @@ -646,7 +646,7 @@ def root_create(request): return redirect('wiki:get', path=root.path) except NoRootURL: pass - if not request.user.is_superuser(): + if not request.user.is_superuser: return redirect(settings.LOGIN_URL + "?next=" + reverse("wiki:root_create")) if request.method == 'POST': create_form = forms.CreateRootForm(request.POST) From 5f1fe411c3ae5f8ad3a7bfa3cfa9fc54b79beab3 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Mon, 10 Feb 2014 14:35:17 -0500 Subject: [PATCH 04/80] Use ugettext_lazy to prevent early translation. --- wiki/core/plugins/base.py | 2 +- wiki/forms.py | 2 +- wiki/plugins/attachments/forms.py | 2 +- wiki/plugins/attachments/wiki_plugin.py | 2 +- wiki/plugins/help/wiki_plugin.py | 2 +- wiki/plugins/images/forms.py | 4 ++-- wiki/plugins/images/wiki_plugin.py | 2 +- wiki/plugins/links/wiki_plugin.py | 2 +- wiki/plugins/notifications/forms.py | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/wiki/core/plugins/base.py b/wiki/core/plugins/base.py index 62290b3d9..533ecc1e2 100644 --- a/wiki/core/plugins/base.py +++ b/wiki/core/plugins/base.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy as _ """Base classes for different plugin objects. diff --git a/wiki/forms.py b/wiki/forms.py index 75f0a3244..daf9eeb19 100644 --- a/wiki/forms.py +++ b/wiki/forms.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from django import forms -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy as _ from django.utils.safestring import mark_safe from django.forms.util import flatatt from django.utils.encoding import force_unicode diff --git a/wiki/plugins/attachments/forms.py b/wiki/plugins/attachments/forms.py index aaf64de9e..47ac27954 100644 --- a/wiki/plugins/attachments/forms.py +++ b/wiki/plugins/attachments/forms.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from django import forms -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy as _ from wiki.plugins.attachments import models diff --git a/wiki/plugins/attachments/wiki_plugin.py b/wiki/plugins/attachments/wiki_plugin.py index 32f9f221f..47c51fce8 100644 --- a/wiki/plugins/attachments/wiki_plugin.py +++ b/wiki/plugins/attachments/wiki_plugin.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from django.conf.urls.defaults import patterns, url -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy as _ from wiki.core.plugins import registry from wiki.core.plugins.base import BasePlugin diff --git a/wiki/plugins/help/wiki_plugin.py b/wiki/plugins/help/wiki_plugin.py index 14b79f9e3..f2b9a6dde 100644 --- a/wiki/plugins/help/wiki_plugin.py +++ b/wiki/plugins/help/wiki_plugin.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from django.conf.urls.defaults import patterns -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy as _ from wiki.core.plugins import registry from wiki.core.plugins.base import BasePlugin diff --git a/wiki/plugins/images/forms.py b/wiki/plugins/images/forms.py index 6f12569df..8debc7049 100644 --- a/wiki/plugins/images/forms.py +++ b/wiki/plugins/images/forms.py @@ -1,5 +1,5 @@ from django import forms -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy as _ from wiki.core.plugins.base import PluginSidebarFormMixin from wiki.plugins.images import models @@ -62,4 +62,4 @@ def clean_confirm(self): confirm = self.cleaned_data['confirm'] if not confirm: raise forms.ValidationError(_(u'You are not sure enough!')) - return confirm \ No newline at end of file + return confirm diff --git a/wiki/plugins/images/wiki_plugin.py b/wiki/plugins/images/wiki_plugin.py index 8a6b1578f..96bcd0abd 100644 --- a/wiki/plugins/images/wiki_plugin.py +++ b/wiki/plugins/images/wiki_plugin.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from django.conf.urls.defaults import patterns, url -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy as _ from wiki.core.plugins import registry from wiki.core.plugins.base import BasePlugin diff --git a/wiki/plugins/links/wiki_plugin.py b/wiki/plugins/links/wiki_plugin.py index 377284a32..4b6f72bdb 100644 --- a/wiki/plugins/links/wiki_plugin.py +++ b/wiki/plugins/links/wiki_plugin.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from django.conf.urls.defaults import patterns, url -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy as _ from wiki.conf import settings from wiki.core.plugins import registry diff --git a/wiki/plugins/notifications/forms.py b/wiki/plugins/notifications/forms.py index d5aceb0a3..8594d909a 100644 --- a/wiki/plugins/notifications/forms.py +++ b/wiki/plugins/notifications/forms.py @@ -1,5 +1,5 @@ from django import forms -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy as _ from django_notify.models import Settings, NotificationType from django.contrib.contenttypes.models import ContentType From 7aa9d0c2901ea68c014ffafe7f7bf9e8a84e4077 Mon Sep 17 00:00:00 2001 From: "Dave St.Germain" Date: Mon, 24 Feb 2014 16:00:44 -0500 Subject: [PATCH 05/80] Fixes MAT-4 --- wiki/forms.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/wiki/forms.py b/wiki/forms.py index daf9eeb19..c0e82a3c5 100644 --- a/wiki/forms.py +++ b/wiki/forms.py @@ -4,7 +4,7 @@ from django.utils.safestring import mark_safe from django.forms.util import flatatt from django.utils.encoding import force_unicode -from django.utils.html import escape, conditional_escape +from django.utils.html import escape, conditional_escape, strip_tags from itertools import chain @@ -85,6 +85,10 @@ def __init__(self, current_revision, *args, **kwargs): super(EditForm, self).__init__(*args, **kwargs) + def clean_title(self): + title = strip_tags(self.cleaned_data['title']) + return title + def clean(self): cd = self.cleaned_data if self.no_clean or self.preview: @@ -206,6 +210,10 @@ def __init__(self, urlpath_parent, *args, **kwargs): summary = forms.CharField(label=_(u'Summary'), help_text=_(u"Write a brief message for the article's history log."), required=False) + def clean_title(self): + title = strip_tags(self.cleaned_data['title']) + return title + def clean_slug(self): slug = self.cleaned_data['slug'] if slug.startswith("_"): From 8b8602b364800be7b305d34ac40f3417d0287018 Mon Sep 17 00:00:00 2001 From: Usman Khalid <2200617@gmail.com> Date: Mon, 21 Sep 2015 22:01:11 +0500 Subject: [PATCH 06/80] Upgrade to Django v1.8 TNL-2830 --- django_notify/decorators.py | 4 +- django_notify/migrations/0001_initial.py | 205 +++---- django_notify/models.py | 1 + django_notify/urls.py | 2 +- requirements.txt | 8 +- testproject/testproject/settings.py | 1 - wiki/admin.py | 2 + wiki/core/http.py | 2 +- wiki/decorators.py | 4 +- wiki/managers.py | 28 +- wiki/migrations/0001_initial.py | 557 ++++++++++-------- ...2_auto__add_field_articleplugin_created.py | 134 ----- .../0003_auto__add_field_urlpath_article.py | 135 ----- .../0004_populate_urlpath__article.py | 139 ----- .../0005_auto__chg_field_urlpath_article.py | 133 ----- ...mentrevision__add_image__add_attachment.py | 198 ------- .../0007_auto__add_articlesubscription.py | 190 ------ ...isionpluginrevision__add_imagerevision_.py | 271 --------- ...n_width__add_field_imagerevision_height.py | 221 ------- ...010_auto__chg_field_imagerevision_image.py | 211 ------- ...n_width__chg_field_imagerevision_height.py | 217 ------- wiki/models/__init__.py | 7 - wiki/models/article.py | 6 +- wiki/models/pluginbase.py | 2 +- wiki/models/urlpath.py | 11 +- .../attachments/migrations/0001_initial.py | 151 ----- wiki/plugins/attachments/models.py | 2 +- wiki/plugins/attachments/views.py | 27 +- wiki/plugins/attachments/wiki_plugin.py | 2 +- wiki/plugins/help/wiki_plugin.py | 2 +- wiki/plugins/images/admin.py | 1 + .../plugins/images/migrations/0001_initial.py | 111 ---- wiki/plugins/images/wiki_plugin.py | 2 +- wiki/plugins/links/wiki_plugin.py | 2 +- .../notifications/migrations/0001_initial.py | 125 ---- wiki/plugins/notifications/models.py | 2 +- .../plugins/notifications/menubaritem.html | 6 +- wiki/urls.py | 2 +- wiki/views/article.py | 40 +- 39 files changed, 462 insertions(+), 2702 deletions(-) delete mode 100644 wiki/migrations/0002_auto__add_field_articleplugin_created.py delete mode 100644 wiki/migrations/0003_auto__add_field_urlpath_article.py delete mode 100644 wiki/migrations/0004_populate_urlpath__article.py delete mode 100644 wiki/migrations/0005_auto__chg_field_urlpath_article.py delete mode 100644 wiki/migrations/0006_auto__add_attachmentrevision__add_image__add_attachment.py delete mode 100644 wiki/migrations/0007_auto__add_articlesubscription.py delete mode 100644 wiki/migrations/0008_auto__add_simpleplugin__add_revisionpluginrevision__add_imagerevision_.py delete mode 100644 wiki/migrations/0009_auto__add_field_imagerevision_width__add_field_imagerevision_height.py delete mode 100644 wiki/migrations/0010_auto__chg_field_imagerevision_image.py delete mode 100644 wiki/migrations/0011_auto__chg_field_imagerevision_width__chg_field_imagerevision_height.py delete mode 100644 wiki/plugins/attachments/migrations/0001_initial.py delete mode 100644 wiki/plugins/images/migrations/0001_initial.py delete mode 100644 wiki/plugins/notifications/migrations/0001_initial.py diff --git a/django_notify/decorators.py b/django_notify/decorators.py index f1d5771d2..7e94c90ee 100644 --- a/django_notify/decorators.py +++ b/django_notify/decorators.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from django.utils import simplejson as json +import json from django.http import HttpResponse from django.contrib.auth.decorators import login_required @@ -37,7 +37,7 @@ def wrap(request, *args, **kwargs): obj = func(request, *args, **kwargs) data = json.dumps(obj, ensure_ascii=False) status = kwargs.get('status', 200) - response = HttpResponse(mimetype='application/json', status=status) + response = HttpResponse(content_type='application/json', status=status) response.write(data) return response return wrap diff --git a/django_notify/migrations/0001_initial.py b/django_notify/migrations/0001_initial.py index 55645602b..b2fd056e4 100644 --- a/django_notify/migrations/0001_initial.py +++ b/django_notify/migrations/0001_initial.py @@ -1,133 +1,84 @@ # -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models +from __future__ import unicode_literals +from django.db import models, migrations +import django.db.models.deletion +from django.conf import settings -class Migration(SchemaMigration): - def forwards(self, orm): - # Adding model 'NotificationType' - db.create_table('notify_notificationtype', ( - ('key', self.gf('django.db.models.fields.CharField')(unique=True, max_length=128, primary_key=True)), - ('label', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)), - ('content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'], null=True, blank=True)), - )) - db.send_create_signal('django_notify', ['NotificationType']) +class Migration(migrations.Migration): - # Adding model 'Settings' - db.create_table('notify_settings', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), - ('interval', self.gf('django.db.models.fields.SmallIntegerField')(default=0)), - )) - db.send_create_signal('django_notify', ['Settings']) + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('contenttypes', '0001_initial'), + ] - # Adding model 'Subscription' - db.create_table('notify_subscription', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('settings', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['django_notify.Settings'])), - ('notification_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['django_notify.NotificationType'])), - ('object_id', self.gf('django.db.models.fields.CharField')(max_length=64, null=True, blank=True)), - ('send_emails', self.gf('django.db.models.fields.BooleanField')(default=True)), - )) - db.send_create_signal('django_notify', ['Subscription']) - - # Adding model 'Notification' - db.create_table('notify_notification', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('subscription', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['django_notify.Subscription'], null=True, on_delete=models.SET_NULL, blank=True)), - ('message', self.gf('django.db.models.fields.TextField')()), - ('url', self.gf('django.db.models.fields.URLField')(max_length=200, null=True, blank=True)), - ('is_viewed', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('is_emailed', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), - )) - db.send_create_signal('django_notify', ['Notification']) - - - def backwards(self, orm): - # Deleting model 'NotificationType' - db.delete_table('notify_notificationtype') - - # Deleting model 'Settings' - db.delete_table('notify_settings') - - # Deleting model 'Subscription' - db.delete_table('notify_subscription') - - # Deleting model 'Notification' - db.delete_table('notify_notification') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'django_notify.notification': { - 'Meta': {'object_name': 'Notification', 'db_table': "'notify_notification'"}, - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_emailed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_viewed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'message': ('django.db.models.fields.TextField', [], {}), - 'subscription': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_notify.Subscription']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), - 'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}) - }, - 'django_notify.notificationtype': { - 'Meta': {'object_name': 'NotificationType', 'db_table': "'notify_notificationtype'"}, - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}), - 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128', 'primary_key': 'True'}), - 'label': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}) - }, - 'django_notify.settings': { - 'Meta': {'object_name': 'Settings', 'db_table': "'notify_settings'"}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'interval': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'django_notify.subscription': { - 'Meta': {'object_name': 'Subscription', 'db_table': "'notify_subscription'"}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'notification_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_notify.NotificationType']"}), - 'object_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), - 'send_emails': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'settings': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_notify.Settings']"}) - } - } - - complete_apps = ['django_notify'] \ No newline at end of file + operations = [ + migrations.CreateModel( + name='Notification', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('message', models.TextField()), + ('url', models.URLField(null=True, verbose_name='link for notification', blank=True)), + ('is_viewed', models.BooleanField(default=False)), + ('is_emailed', models.BooleanField(default=False)), + ('created', models.DateTimeField(auto_now_add=True)), + ], + options={ + 'db_table': 'notify_notification', + 'verbose_name': 'notification', + 'verbose_name_plural': 'notifications', + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='NotificationType', + fields=[ + ('key', models.CharField(max_length=128, unique=True, serialize=False, verbose_name='unique key', primary_key=True)), + ('label', models.CharField(max_length=128, null=True, verbose_name='verbose name', blank=True)), + ('content_type', models.ForeignKey(blank=True, to='contenttypes.ContentType', null=True)), + ], + options={ + 'db_table': 'notify_notificationtype', + 'verbose_name': 'type', + 'verbose_name_plural': 'types', + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='Settings', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('interval', models.SmallIntegerField(default=0, verbose_name='interval', choices=[(0, 'instantly'), (23, 'daily'), (167, 'weekly')])), + ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), + ], + options={ + 'db_table': 'notify_settings', + 'verbose_name': 'settings', + 'verbose_name_plural': 'settings', + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='Subscription', + fields=[ + ('subscription_id', models.AutoField(serialize=False, primary_key=True)), + ('object_id', models.CharField(help_text='Leave this blank to subscribe to any kind of object', max_length=64, null=True, blank=True)), + ('send_emails', models.BooleanField(default=True)), + ('notification_type', models.ForeignKey(to='django_notify.NotificationType')), + ('settings', models.ForeignKey(to='django_notify.Settings')), + ], + options={ + 'db_table': 'notify_subscription', + 'verbose_name': 'subscription', + 'verbose_name_plural': 'subscriptions', + }, + bases=(models.Model,), + ), + migrations.AddField( + model_name='notification', + name='subscription', + field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, blank=True, to='django_notify.Subscription', null=True), + preserve_default=True, + ), + ] diff --git a/django_notify/models.py b/django_notify/models.py index db80a26c8..0a2047671 100644 --- a/django_notify/models.py +++ b/django_notify/models.py @@ -42,6 +42,7 @@ class Meta: class Subscription(models.Model): + subscription_id = models.AutoField(primary_key=True) settings = models.ForeignKey(Settings) notification_type = models.ForeignKey(NotificationType) object_id = models.CharField(max_length=64, null=True, blank=True, diff --git a/django_notify/urls.py b/django_notify/urls.py index b5270f3b1..50cd32e93 100644 --- a/django_notify/urls.py +++ b/django_notify/urls.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from django.conf.urls.defaults import patterns, url +from django.conf.urls import patterns, url urlpatterns = patterns('', url('^json/get/$', 'django_notify.views.get_notifications', name='json_get', kwargs={}), diff --git a/requirements.txt b/requirements.txt index e59f547d7..45bf5c952 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -django>=1.4 -South<0.8 +django<1.9 +South==1.0.1 Markdown<2.3.0 django-sekizai<0.7 -django-mptt>=0.5.3 -sorl-thumbnail \ No newline at end of file +django-mptt<0.8 +sorl-thumbnail<13 \ No newline at end of file diff --git a/testproject/testproject/settings.py b/testproject/testproject/settings.py index 03c6d64ab..0ec4f1f9c 100644 --- a/testproject/testproject/settings.py +++ b/testproject/testproject/settings.py @@ -103,7 +103,6 @@ 'django.contrib.staticfiles', 'django.contrib.admin', 'django.contrib.admindocs', - 'south', 'sekizai', 'django_notify', 'sorl.thumbnail', diff --git a/wiki/admin.py b/wiki/admin.py index 3d0e86b8b..f4ceac832 100644 --- a/wiki/admin.py +++ b/wiki/admin.py @@ -16,6 +16,7 @@ class ArticleRevisionForm(forms.ModelForm): class Meta: model = models.ArticleRevision + fields = '__all__' def __init__(self, *args, **kwargs): super(ArticleRevisionForm, self).__init__(*args, **kwargs) @@ -44,6 +45,7 @@ class ArticleForm(forms.ModelForm): class Meta: model = models.Article + fields = '__all__' def __init__(self, *args, **kwargs): super(ArticleForm, self).__init__(*args, **kwargs) diff --git a/wiki/core/http.py b/wiki/core/http.py index dd4e70d3e..15a2622fc 100644 --- a/wiki/core/http.py +++ b/wiki/core/http.py @@ -16,7 +16,7 @@ def send_file(request, filepath, last_modified=None, filename=None): mimetype, encoding = mimetypes.guess_type(fullpath) mimetype = mimetype or 'application/octet-stream' - response = HttpResponse(open(fullpath, 'rb').read(), mimetype=mimetype) + response = HttpResponse(open(fullpath, 'rb').read(), content_type=mimetype) if not last_modified: response["Last-Modified"] = http_date(statobj.st_mtime) diff --git a/wiki/decorators.py b/wiki/decorators.py index ebe17161b..b5c6aafc0 100644 --- a/wiki/decorators.py +++ b/wiki/decorators.py @@ -6,7 +6,7 @@ from django.shortcuts import redirect, get_object_or_404 from django.template.context import RequestContext from django.template.loader import render_to_string -from django.utils import simplejson as json +import json from wiki.core.exceptions import NoRootURL @@ -15,7 +15,7 @@ def wrap(request, *args, **kwargs): obj = func(request, *args, **kwargs) data = json.dumps(obj, ensure_ascii=False) status = kwargs.get('status', 200) - response = HttpResponse(mimetype='application/json', status=status) + response = HttpResponse(content_type='application/json', status=status) response.write(data) return response return wrap diff --git a/wiki/managers.py b/wiki/managers.py index d3d675d6c..4ba864b94 100644 --- a/wiki/managers.py +++ b/wiki/managers.py @@ -94,26 +94,26 @@ class ArticleFkEmptyQuerySet(ArticleFkEmptyQuerySetMixin, EmptyQuerySet): class ArticleManager(models.Manager): def get_empty_query_set(self): return ArticleEmptyQuerySet(model=self.model) - def get_query_set(self): + def get_queryset(self): return ArticleQuerySet(self.model, using=self._db) def active(self): - return self.get_query_set().active() + return self.get_queryset().active() def can_read(self, user): - return self.get_query_set().can_read(user) + return self.get_queryset().can_read(user) def can_write(self, user): - return self.get_query_set().can_write(user) + return self.get_queryset().can_write(user) class ArticleFkManager(models.Manager): def get_empty_query_set(self): return ArticleFkEmptyQuerySet(model=self.model) - def get_query_set(self): + def get_queryset(self): return ArticleFkQuerySet(self.model, using=self._db) def active(self): - return self.get_query_set().active() + return self.get_queryset().active() def can_read(self, user): - return self.get_query_set().can_read(user) + return self.get_queryset().can_read(user) def can_write(self, user): - return self.get_query_set().can_write(user) + return self.get_queryset().can_write(user) class URLPathEmptyQuerySet(EmptyQuerySet, ArticleFkEmptyQuerySetMixin): @@ -127,20 +127,20 @@ def select_related_common(self): return self.select_related("parent", "article__current_revision", "article__owner") class URLPathManager(TreeManager): - + def get_empty_query_set(self): return URLPathEmptyQuerySet(model=self.model) - def get_query_set(self): + def get_queryset(self): """Return a QuerySet with the same ordering as the TreeManager.""" return URLPathQuerySet(self.model, using=self._db).order_by( self.tree_id_attr, self.left_attr) def select_related_common(self): - return self.get_query_set().common_select_related() + return self.get_queryset().common_select_related() def active(self): - return self.get_query_set().active() + return self.get_queryset().active() def can_read(self, user): - return self.get_query_set().can_read(user) + return self.get_queryset().can_read(user) def can_write(self, user): - return self.get_query_set().can_write(user) + return self.get_queryset().can_write(user) diff --git a/wiki/migrations/0001_initial.py b/wiki/migrations/0001_initial.py index 3ebef08c5..72f7b8b16 100644 --- a/wiki/migrations/0001_initial.py +++ b/wiki/migrations/0001_initial.py @@ -1,254 +1,315 @@ # -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models +from __future__ import unicode_literals +from django.db import models, migrations +import mptt.fields +import wiki.plugins.images.models +import wiki.plugins.attachments.models +from django.conf import settings -class Migration(SchemaMigration): - def forwards(self, orm): - # Adding model 'Article' - db.create_table('wiki_article', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('current_revision', self.gf('django.db.models.fields.related.OneToOneField')(blank=True, related_name='current_set', unique=True, null=True, to=orm['wiki.ArticleRevision'])), - ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), - ('modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), - ('owner', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)), - ('group', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.Group'], null=True, blank=True)), - ('group_read', self.gf('django.db.models.fields.BooleanField')(default=True)), - ('group_write', self.gf('django.db.models.fields.BooleanField')(default=True)), - ('other_read', self.gf('django.db.models.fields.BooleanField')(default=True)), - ('other_write', self.gf('django.db.models.fields.BooleanField')(default=True)), - )) - db.send_create_signal('wiki', ['Article']) +class Migration(migrations.Migration): - # Adding model 'ArticleForObject' - db.create_table('wiki_articleforobject', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('article', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['wiki.Article'])), - ('content_type', self.gf('django.db.models.fields.related.ForeignKey')(related_name='content_type_set_for_articleforobject', to=orm['contenttypes.ContentType'])), - ('object_id', self.gf('django.db.models.fields.PositiveIntegerField')()), - ('is_mptt', self.gf('django.db.models.fields.BooleanField')(default=False)), - )) - db.send_create_signal('wiki', ['ArticleForObject']) + dependencies = [ + ('django_notify', '0001_initial'), + ('auth', '0001_initial'), + ('sites', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('contenttypes', '0001_initial'), + ] - # Adding unique constraint on 'ArticleForObject', fields ['content_type', 'object_id'] - db.create_unique('wiki_articleforobject', ['content_type_id', 'object_id']) - - # Adding model 'ArticleRevision' - db.create_table('wiki_articlerevision', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('revision_number', self.gf('django.db.models.fields.IntegerField')()), - ('user_message', self.gf('django.db.models.fields.TextField')(blank=True)), - ('automatic_log', self.gf('django.db.models.fields.TextField')(blank=True)), - ('ip_address', self.gf('django.db.models.fields.IPAddressField')(max_length=15, null=True, blank=True)), - ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)), - ('modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), - ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), - ('previous_revision', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['wiki.ArticleRevision'], null=True, blank=True)), - ('deleted', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('locked', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('article', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['wiki.Article'])), - ('content', self.gf('django.db.models.fields.TextField')(blank=True)), - ('title', self.gf('django.db.models.fields.CharField')(max_length=512)), - ('redirect', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='redirect_set', null=True, to=orm['wiki.Article'])), - )) - db.send_create_signal('wiki', ['ArticleRevision']) - - # Adding unique constraint on 'ArticleRevision', fields ['article', 'revision_number'] - db.create_unique('wiki_articlerevision', ['article_id', 'revision_number']) - - # Adding model 'URLPath' - db.create_table('wiki_urlpath', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('slug', self.gf('django.db.models.fields.SlugField')(max_length=50, null=True, blank=True)), - ('site', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['sites.Site'])), - ('parent', self.gf('mptt.fields.TreeForeignKey')(blank=True, related_name='children', null=True, to=orm['wiki.URLPath'])), - ('lft', self.gf('django.db.models.fields.PositiveIntegerField')(db_index=True)), - ('rght', self.gf('django.db.models.fields.PositiveIntegerField')(db_index=True)), - ('tree_id', self.gf('django.db.models.fields.PositiveIntegerField')(db_index=True)), - ('level', self.gf('django.db.models.fields.PositiveIntegerField')(db_index=True)), - )) - db.send_create_signal('wiki', ['URLPath']) - - # Adding unique constraint on 'URLPath', fields ['site', 'parent', 'slug'] - db.create_unique('wiki_urlpath', ['site_id', 'parent_id', 'slug']) - - # Adding model 'ArticlePlugin' - db.create_table('wiki_articleplugin', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('article', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['wiki.Article'])), - ('deleted', self.gf('django.db.models.fields.BooleanField')(default=False)), - )) - db.send_create_signal('wiki', ['ArticlePlugin']) - - # Adding model 'ReusablePlugin' - db.create_table('wiki_reusableplugin', ( - ('articleplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['wiki.ArticlePlugin'], unique=True, primary_key=True)), - )) - db.send_create_signal('wiki', ['ReusablePlugin']) - - # Adding M2M table for field articles on 'ReusablePlugin' - db.create_table('wiki_reusableplugin_articles', ( - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('reusableplugin', models.ForeignKey(orm['wiki.reusableplugin'], null=False)), - ('article', models.ForeignKey(orm['wiki.article'], null=False)) - )) - db.create_unique('wiki_reusableplugin_articles', ['reusableplugin_id', 'article_id']) - - # Adding model 'RevisionPlugin' - db.create_table('wiki_revisionplugin', ( - ('articleplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['wiki.ArticlePlugin'], unique=True, primary_key=True)), - ('revision', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['wiki.ArticleRevision'])), - )) - db.send_create_signal('wiki', ['RevisionPlugin']) - - - def backwards(self, orm): - # Removing unique constraint on 'URLPath', fields ['site', 'parent', 'slug'] - db.delete_unique('wiki_urlpath', ['site_id', 'parent_id', 'slug']) - - # Removing unique constraint on 'ArticleRevision', fields ['article', 'revision_number'] - db.delete_unique('wiki_articlerevision', ['article_id', 'revision_number']) - - # Removing unique constraint on 'ArticleForObject', fields ['content_type', 'object_id'] - db.delete_unique('wiki_articleforobject', ['content_type_id', 'object_id']) - - # Deleting model 'Article' - db.delete_table('wiki_article') - - # Deleting model 'ArticleForObject' - db.delete_table('wiki_articleforobject') - - # Deleting model 'ArticleRevision' - db.delete_table('wiki_articlerevision') - - # Deleting model 'URLPath' - db.delete_table('wiki_urlpath') - - # Deleting model 'ArticlePlugin' - db.delete_table('wiki_articleplugin') - - # Deleting model 'ReusablePlugin' - db.delete_table('wiki_reusableplugin') - - # Removing M2M table for field articles on 'ReusablePlugin' - db.delete_table('wiki_reusableplugin_articles') - - # Deleting model 'RevisionPlugin' - db.delete_table('wiki_revisionplugin') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'sites.site': { - 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, - 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'wiki.article': { - 'Meta': {'object_name': 'Article'}, - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.ArticleRevision']"}), - 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), - 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'other_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'other_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) - }, - 'wiki.articleforobject': { - 'Meta': {'unique_together': "(('content_type', 'object_id'),)", 'object_name': 'ArticleForObject'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_type_set_for_articleforobject'", 'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_mptt': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}) - }, - 'wiki.articleplugin': { - 'Meta': {'object_name': 'ArticlePlugin'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'wiki.articlerevision': { - 'Meta': {'ordering': "('created',)", 'unique_together': "(('article', 'revision_number'),)", 'object_name': 'ArticleRevision'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']", 'null': 'True', 'blank': 'True'}), - 'redirect': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'redirect_set'", 'null': 'True', 'to': "orm['wiki.Article']"}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '512'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.reusableplugin': { - 'Meta': {'object_name': 'ReusablePlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'articles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'shared_plugins_set'", 'symmetrical': 'False', 'to': "orm['wiki.Article']"}) - }, - 'wiki.revisionplugin': { - 'Meta': {'object_name': 'RevisionPlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']"}) - }, - 'wiki.urlpath': { - 'Meta': {'unique_together': "(('site', 'parent', 'slug'),)", 'object_name': 'URLPath'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'parent': ('mptt.fields.TreeForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['wiki.URLPath']"}), - 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), - 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}) - } - } - - complete_apps = ['wiki'] \ No newline at end of file + operations = [ + migrations.CreateModel( + name='Article', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('created', models.DateTimeField(auto_now_add=True, verbose_name='created')), + ('modified', models.DateTimeField(help_text='Article properties last modified', verbose_name='modified', auto_now=True)), + ('group_read', models.BooleanField(default=True, verbose_name='group read access')), + ('group_write', models.BooleanField(default=True, verbose_name='group write access')), + ('other_read', models.BooleanField(default=True, verbose_name='others read access')), + ('other_write', models.BooleanField(default=True, verbose_name='others write access')), + ], + options={ + 'permissions': (('moderate', 'Can edit all articles and lock/unlock/restore'), ('assign', 'Can change ownership of any article'), ('grant', 'Can assign permissions to other users')), + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='ArticleForObject', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('object_id', models.PositiveIntegerField(verbose_name='object ID')), + ('is_mptt', models.BooleanField(default=False, editable=False)), + ('article', models.ForeignKey(to='wiki.Article')), + ('content_type', models.ForeignKey(related_name='content_type_set_for_articleforobject', verbose_name='content type', to='contenttypes.ContentType')), + ], + options={ + 'verbose_name': 'Article for object', + 'verbose_name_plural': 'Articles for object', + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='ArticlePlugin', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('deleted', models.BooleanField(default=False)), + ('created', models.DateTimeField(auto_now_add=True)), + ], + options={ + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='ArticleRevision', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('revision_number', models.IntegerField(verbose_name='revision number', editable=False)), + ('user_message', models.TextField(blank=True)), + ('automatic_log', models.TextField(editable=False, blank=True)), + ('ip_address', models.IPAddressField(verbose_name='IP address', null=True, editable=False, blank=True)), + ('modified', models.DateTimeField(auto_now=True)), + ('created', models.DateTimeField(auto_now_add=True)), + ('deleted', models.BooleanField(default=False, verbose_name='deleted')), + ('locked', models.BooleanField(default=False, verbose_name='locked')), + ('content', models.TextField(verbose_name='article contents', blank=True)), + ('title', models.CharField(help_text='Each revision contains a title field that must be filled out, even if the title has not changed', max_length=512, verbose_name='article title')), + ('article', models.ForeignKey(verbose_name='article', to='wiki.Article')), + ('previous_revision', models.ForeignKey(blank=True, to='wiki.ArticleRevision', null=True)), + ('user', models.ForeignKey(verbose_name='user', blank=True, to=settings.AUTH_USER_MODEL, null=True)), + ], + options={ + 'ordering': ('created',), + 'get_latest_by': 'revision_number', + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='ArticleSubscription', + fields=[ + ('subscription_ptr', models.OneToOneField(parent_link=True, auto_created=True, to='django_notify.Subscription')), + ('articleplugin_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='wiki.ArticlePlugin')), + ], + options={ + }, + bases=('wiki.articleplugin', 'django_notify.subscription'), + ), + migrations.CreateModel( + name='AttachmentRevision', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('revision_number', models.IntegerField(verbose_name='revision number', editable=False)), + ('user_message', models.TextField(blank=True)), + ('automatic_log', models.TextField(editable=False, blank=True)), + ('ip_address', models.IPAddressField(verbose_name='IP address', null=True, editable=False, blank=True)), + ('modified', models.DateTimeField(auto_now=True)), + ('created', models.DateTimeField(auto_now_add=True)), + ('deleted', models.BooleanField(default=False, verbose_name='deleted')), + ('locked', models.BooleanField(default=False, verbose_name='locked')), + ('file', models.FileField(upload_to=wiki.plugins.attachments.models.upload_path, verbose_name='file')), + ('description', models.TextField(blank=True)), + ], + options={ + 'ordering': ('created',), + 'get_latest_by': 'revision_number', + 'verbose_name': 'attachment revision', + 'verbose_name_plural': 'attachment revisions', + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='ReusablePlugin', + fields=[ + ('articleplugin_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='wiki.ArticlePlugin')), + ], + options={ + }, + bases=('wiki.articleplugin',), + ), + migrations.CreateModel( + name='Attachment', + fields=[ + ('reusableplugin_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='wiki.ReusablePlugin')), + ('original_filename', models.CharField(max_length=256, null=True, verbose_name='original filename', blank=True)), + ], + options={ + 'verbose_name': 'attachment', + 'verbose_name_plural': 'attachments', + }, + bases=('wiki.reusableplugin',), + ), + migrations.CreateModel( + name='RevisionPlugin', + fields=[ + ('articleplugin_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='wiki.ArticlePlugin')), + ], + options={ + }, + bases=('wiki.articleplugin',), + ), + migrations.CreateModel( + name='Image', + fields=[ + ('revisionplugin_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='wiki.RevisionPlugin')), + ], + options={ + 'verbose_name': 'image', + 'verbose_name_plural': 'images', + }, + bases=('wiki.revisionplugin',), + ), + migrations.CreateModel( + name='RevisionPluginRevision', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('revision_number', models.IntegerField(verbose_name='revision number', editable=False)), + ('user_message', models.TextField(blank=True)), + ('automatic_log', models.TextField(editable=False, blank=True)), + ('ip_address', models.IPAddressField(verbose_name='IP address', null=True, editable=False, blank=True)), + ('modified', models.DateTimeField(auto_now=True)), + ('created', models.DateTimeField(auto_now_add=True)), + ('deleted', models.BooleanField(default=False, verbose_name='deleted')), + ('locked', models.BooleanField(default=False, verbose_name='locked')), + ], + options={ + 'ordering': ('-created',), + 'get_latest_by': 'revision_number', + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='ImageRevision', + fields=[ + ('revisionpluginrevision_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='wiki.RevisionPluginRevision')), + ('image', models.ImageField(upload_to=wiki.plugins.images.models.upload_path, width_field=b'width', height_field=b'height', max_length=2000, blank=True, null=True)), + ('width', models.SmallIntegerField(null=True, blank=True)), + ('height', models.SmallIntegerField(null=True, blank=True)), + ], + options={ + 'ordering': ('-created',), + 'verbose_name': 'image revision', + 'verbose_name_plural': 'image revisions', + }, + bases=('wiki.revisionpluginrevision',), + ), + migrations.CreateModel( + name='SimplePlugin', + fields=[ + ('articleplugin_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='wiki.ArticlePlugin')), + ('article_revision', models.ForeignKey(to='wiki.ArticleRevision')), + ], + options={ + }, + bases=('wiki.articleplugin',), + ), + migrations.CreateModel( + name='URLPath', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('slug', models.SlugField(null=True, verbose_name='slug', blank=True)), + ('lft', models.PositiveIntegerField(editable=False, db_index=True)), + ('rght', models.PositiveIntegerField(editable=False, db_index=True)), + ('tree_id', models.PositiveIntegerField(editable=False, db_index=True)), + ('level', models.PositiveIntegerField(editable=False, db_index=True)), + ('article', models.ForeignKey(editable=False, to='wiki.Article', verbose_name='Cache lookup value for articles')), + ('parent', mptt.fields.TreeForeignKey(related_name='children', blank=True, to='wiki.URLPath', null=True)), + ('site', models.ForeignKey(to='sites.Site')), + ], + options={ + 'verbose_name': 'URL path', + 'verbose_name_plural': 'URL paths', + }, + bases=(models.Model,), + ), + migrations.AlterUniqueTogether( + name='urlpath', + unique_together=set([('site', 'parent', 'slug')]), + ), + migrations.AddField( + model_name='revisionpluginrevision', + name='plugin', + field=models.ForeignKey(related_name='revision_set', to='wiki.RevisionPlugin'), + preserve_default=True, + ), + migrations.AddField( + model_name='revisionpluginrevision', + name='previous_revision', + field=models.ForeignKey(blank=True, to='wiki.RevisionPluginRevision', null=True), + preserve_default=True, + ), + migrations.AddField( + model_name='revisionpluginrevision', + name='user', + field=models.ForeignKey(verbose_name='user', blank=True, to=settings.AUTH_USER_MODEL, null=True), + preserve_default=True, + ), + migrations.AddField( + model_name='revisionplugin', + name='current_revision', + field=models.OneToOneField(related_name='plugin_set', null=True, to='wiki.RevisionPluginRevision', blank=True, help_text='The revision being displayed for this plugin.If you need to do a roll-back, simply change the value of this field.', verbose_name='current revision'), + preserve_default=True, + ), + migrations.AddField( + model_name='reusableplugin', + name='articles', + field=models.ManyToManyField(related_name='shared_plugins_set', to='wiki.Article'), + preserve_default=True, + ), + migrations.AddField( + model_name='attachmentrevision', + name='attachment', + field=models.ForeignKey(to='wiki.Attachment'), + preserve_default=True, + ), + migrations.AddField( + model_name='attachmentrevision', + name='previous_revision', + field=models.ForeignKey(blank=True, to='wiki.AttachmentRevision', null=True), + preserve_default=True, + ), + migrations.AddField( + model_name='attachmentrevision', + name='user', + field=models.ForeignKey(verbose_name='user', blank=True, to=settings.AUTH_USER_MODEL, null=True), + preserve_default=True, + ), + migrations.AddField( + model_name='attachment', + name='current_revision', + field=models.OneToOneField(related_name='current_set', null=True, to='wiki.AttachmentRevision', blank=True, help_text='The revision of this attachment currently in use (on all articles using the attachment)', verbose_name='current revision'), + preserve_default=True, + ), + migrations.AlterUniqueTogether( + name='articlerevision', + unique_together=set([('article', 'revision_number')]), + ), + migrations.AddField( + model_name='articleplugin', + name='article', + field=models.ForeignKey(verbose_name='article', to='wiki.Article'), + preserve_default=True, + ), + migrations.AlterUniqueTogether( + name='articleforobject', + unique_together=set([('content_type', 'object_id')]), + ), + migrations.AddField( + model_name='article', + name='current_revision', + field=models.OneToOneField(related_name='current_set', null=True, to='wiki.ArticleRevision', blank=True, help_text='The revision being displayed for this article. If you need to do a roll-back, simply change the value of this field.', verbose_name='current revision'), + preserve_default=True, + ), + migrations.AddField( + model_name='article', + name='group', + field=models.ForeignKey(blank=True, to='auth.Group', help_text='Like in a UNIX file system, permissions can be given to a user according to group membership. Groups are handled through the Django auth system.', null=True, verbose_name='group'), + preserve_default=True, + ), + migrations.AddField( + model_name='article', + name='owner', + field=models.ForeignKey(related_name='owned_articles', blank=True, to=settings.AUTH_USER_MODEL, help_text='The owner of the article, usually the creator. The owner always has both read and write access.', null=True, verbose_name='owner'), + preserve_default=True, + ), + ] diff --git a/wiki/migrations/0002_auto__add_field_articleplugin_created.py b/wiki/migrations/0002_auto__add_field_articleplugin_created.py deleted file mode 100644 index fb1fbe696..000000000 --- a/wiki/migrations/0002_auto__add_field_articleplugin_created.py +++ /dev/null @@ -1,134 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding field 'ArticlePlugin.created' - db.add_column('wiki_articleplugin', 'created', - self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, default=datetime.datetime(2012, 8, 16, 0, 0), blank=True), - keep_default=False) - - - def backwards(self, orm): - # Deleting field 'ArticlePlugin.created' - db.delete_column('wiki_articleplugin', 'created') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'sites.site': { - 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, - 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'wiki.article': { - 'Meta': {'object_name': 'Article'}, - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.ArticleRevision']"}), - 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), - 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'other_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'other_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'owned_articles'", 'null': 'True', 'to': "orm['auth.User']"}) - }, - 'wiki.articleforobject': { - 'Meta': {'unique_together': "(('content_type', 'object_id'),)", 'object_name': 'ArticleForObject'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_type_set_for_articleforobject'", 'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_mptt': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}) - }, - 'wiki.articleplugin': { - 'Meta': {'object_name': 'ArticlePlugin'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'wiki.articlerevision': { - 'Meta': {'ordering': "('created',)", 'unique_together': "(('article', 'revision_number'),)", 'object_name': 'ArticleRevision'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']", 'null': 'True', 'blank': 'True'}), - 'redirect': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'redirect_set'", 'null': 'True', 'to': "orm['wiki.Article']"}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '512'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.reusableplugin': { - 'Meta': {'object_name': 'ReusablePlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'articles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'shared_plugins_set'", 'symmetrical': 'False', 'to': "orm['wiki.Article']"}) - }, - 'wiki.revisionplugin': { - 'Meta': {'object_name': 'RevisionPlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']"}) - }, - 'wiki.urlpath': { - 'Meta': {'unique_together': "(('site', 'parent', 'slug'),)", 'object_name': 'URLPath'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'parent': ('mptt.fields.TreeForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['wiki.URLPath']"}), - 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), - 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}) - } - } - - complete_apps = ['wiki'] \ No newline at end of file diff --git a/wiki/migrations/0003_auto__add_field_urlpath_article.py b/wiki/migrations/0003_auto__add_field_urlpath_article.py deleted file mode 100644 index b2ea376eb..000000000 --- a/wiki/migrations/0003_auto__add_field_urlpath_article.py +++ /dev/null @@ -1,135 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding field 'URLPath.article' - db.add_column('wiki_urlpath', 'article', - self.gf('django.db.models.fields.related.ForeignKey')(to=orm['wiki.Article'], null=True, blank=True), - keep_default=False) - - - def backwards(self, orm): - # Deleting field 'URLPath.article' - db.delete_column('wiki_urlpath', 'article_id') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'sites.site': { - 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, - 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'wiki.article': { - 'Meta': {'object_name': 'Article'}, - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.ArticleRevision']"}), - 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), - 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'other_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'other_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'owned_articles'", 'null': 'True', 'to': "orm['auth.User']"}) - }, - 'wiki.articleforobject': { - 'Meta': {'unique_together': "(('content_type', 'object_id'),)", 'object_name': 'ArticleForObject'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_type_set_for_articleforobject'", 'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_mptt': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}) - }, - 'wiki.articleplugin': { - 'Meta': {'object_name': 'ArticlePlugin'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'wiki.articlerevision': { - 'Meta': {'ordering': "('created',)", 'unique_together': "(('article', 'revision_number'),)", 'object_name': 'ArticleRevision'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']", 'null': 'True', 'blank': 'True'}), - 'redirect': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'redirect_set'", 'null': 'True', 'to': "orm['wiki.Article']"}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '512'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.reusableplugin': { - 'Meta': {'object_name': 'ReusablePlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'articles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'shared_plugins_set'", 'symmetrical': 'False', 'to': "orm['wiki.Article']"}) - }, - 'wiki.revisionplugin': { - 'Meta': {'object_name': 'RevisionPlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']"}) - }, - 'wiki.urlpath': { - 'Meta': {'unique_together': "(('site', 'parent', 'slug'),)", 'object_name': 'URLPath'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']", 'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'parent': ('mptt.fields.TreeForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['wiki.URLPath']"}), - 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), - 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}) - } - } - - complete_apps = ['wiki'] \ No newline at end of file diff --git a/wiki/migrations/0004_populate_urlpath__article.py b/wiki/migrations/0004_populate_urlpath__article.py deleted file mode 100644 index 850e284b7..000000000 --- a/wiki/migrations/0004_populate_urlpath__article.py +++ /dev/null @@ -1,139 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import DataMigration -from django.db import models - -class Migration(DataMigration): - - def forwards(self, orm): - "Write your forwards methods here." - from wiki.models import URLPath - for urlpath in orm['wiki.URLPath'].objects.all(): - # This is stupid... first we need to get the URL path from the Django model - # because south doesn't handle the generic framework... - urlpath2 = URLPath.objects.get(id=urlpath.id) - # ...and then we need to get the same article from the South ORM! - article2 = orm['wiki.Article'].objects.get(id=urlpath2.articles.all()[0].article.id) - urlpath.article = article2 - urlpath.save() - - def backwards(self, orm): - "Write your backwards methods here." - pass - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'sites.site': { - 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, - 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'wiki.article': { - 'Meta': {'object_name': 'Article'}, - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.ArticleRevision']"}), - 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), - 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'other_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'other_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'owned_articles'", 'null': 'True', 'to': "orm['auth.User']"}) - }, - 'wiki.articleforobject': { - 'Meta': {'unique_together': "(('content_type', 'object_id'),)", 'object_name': 'ArticleForObject'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_type_set_for_articleforobject'", 'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_mptt': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}) - }, - 'wiki.articleplugin': { - 'Meta': {'object_name': 'ArticlePlugin'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'wiki.articlerevision': { - 'Meta': {'ordering': "('created',)", 'unique_together': "(('article', 'revision_number'),)", 'object_name': 'ArticleRevision'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']", 'null': 'True', 'blank': 'True'}), - 'redirect': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'redirect_set'", 'null': 'True', 'to': "orm['wiki.Article']"}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '512'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.reusableplugin': { - 'Meta': {'object_name': 'ReusablePlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'articles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'shared_plugins_set'", 'symmetrical': 'False', 'to': "orm['wiki.Article']"}) - }, - 'wiki.revisionplugin': { - 'Meta': {'object_name': 'RevisionPlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']"}) - }, - 'wiki.urlpath': { - 'Meta': {'unique_together': "(('site', 'parent', 'slug'),)", 'object_name': 'URLPath'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']", 'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'parent': ('mptt.fields.TreeForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['wiki.URLPath']"}), - 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), - 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}) - } - } - - complete_apps = ['wiki'] - symmetrical = True diff --git a/wiki/migrations/0005_auto__chg_field_urlpath_article.py b/wiki/migrations/0005_auto__chg_field_urlpath_article.py deleted file mode 100644 index 220213f10..000000000 --- a/wiki/migrations/0005_auto__chg_field_urlpath_article.py +++ /dev/null @@ -1,133 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - - # Changing field 'URLPath.article' - db.alter_column('wiki_urlpath', 'article_id', self.gf('django.db.models.fields.related.ForeignKey')(default=1, to=orm['wiki.Article'])) - - def backwards(self, orm): - - # Changing field 'URLPath.article' - db.alter_column('wiki_urlpath', 'article_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['wiki.Article'], null=True)) - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'sites.site': { - 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, - 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'wiki.article': { - 'Meta': {'object_name': 'Article'}, - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.ArticleRevision']"}), - 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), - 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'other_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'other_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'owned_articles'", 'null': 'True', 'to': "orm['auth.User']"}) - }, - 'wiki.articleforobject': { - 'Meta': {'unique_together': "(('content_type', 'object_id'),)", 'object_name': 'ArticleForObject'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_type_set_for_articleforobject'", 'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_mptt': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}) - }, - 'wiki.articleplugin': { - 'Meta': {'object_name': 'ArticlePlugin'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'wiki.articlerevision': { - 'Meta': {'ordering': "('created',)", 'unique_together': "(('article', 'revision_number'),)", 'object_name': 'ArticleRevision'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']", 'null': 'True', 'blank': 'True'}), - 'redirect': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'redirect_set'", 'null': 'True', 'to': "orm['wiki.Article']"}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '512'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.reusableplugin': { - 'Meta': {'object_name': 'ReusablePlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'articles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'shared_plugins_set'", 'symmetrical': 'False', 'to': "orm['wiki.Article']"}) - }, - 'wiki.revisionplugin': { - 'Meta': {'object_name': 'RevisionPlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']"}) - }, - 'wiki.urlpath': { - 'Meta': {'unique_together': "(('site', 'parent', 'slug'),)", 'object_name': 'URLPath'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'parent': ('mptt.fields.TreeForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['wiki.URLPath']"}), - 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), - 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}) - } - } - - complete_apps = ['wiki'] \ No newline at end of file diff --git a/wiki/migrations/0006_auto__add_attachmentrevision__add_image__add_attachment.py b/wiki/migrations/0006_auto__add_attachmentrevision__add_image__add_attachment.py deleted file mode 100644 index 8cb08ac8a..000000000 --- a/wiki/migrations/0006_auto__add_attachmentrevision__add_image__add_attachment.py +++ /dev/null @@ -1,198 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding model 'AttachmentRevision' - db.create_table('wiki_attachmentrevision', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('revision_number', self.gf('django.db.models.fields.IntegerField')()), - ('user_message', self.gf('django.db.models.fields.TextField')(blank=True)), - ('automatic_log', self.gf('django.db.models.fields.TextField')(blank=True)), - ('ip_address', self.gf('django.db.models.fields.IPAddressField')(max_length=15, null=True, blank=True)), - ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)), - ('modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), - ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), - ('previous_revision', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['wiki.AttachmentRevision'], null=True, blank=True)), - ('deleted', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('locked', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('attachment', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['wiki.Attachment'])), - ('file', self.gf('django.db.models.fields.files.FileField')(max_length=100)), - ('description', self.gf('django.db.models.fields.TextField')(blank=True)), - )) - db.send_create_signal('wiki', ['AttachmentRevision']) - - # Adding model 'Image' - db.create_table('wiki_image', ( - ('revisionplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['wiki.RevisionPlugin'], unique=True, primary_key=True)), - ('image', self.gf('django.db.models.fields.files.ImageField')(max_length=2000)), - )) - db.send_create_signal('wiki', ['Image']) - - # Adding model 'Attachment' - db.create_table('wiki_attachment', ( - ('reusableplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['wiki.ReusablePlugin'], unique=True, primary_key=True)), - ('current_revision', self.gf('django.db.models.fields.related.OneToOneField')(blank=True, related_name='current_set', unique=True, null=True, to=orm['wiki.AttachmentRevision'])), - ('original_filename', self.gf('django.db.models.fields.CharField')(max_length=256, null=True, blank=True)), - )) - db.send_create_signal('wiki', ['Attachment']) - - - def backwards(self, orm): - # Deleting model 'AttachmentRevision' - db.delete_table('wiki_attachmentrevision') - - # Deleting model 'Image' - db.delete_table('wiki_image') - - # Deleting model 'Attachment' - db.delete_table('wiki_attachment') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'sites.site': { - 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, - 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'wiki.article': { - 'Meta': {'object_name': 'Article'}, - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.ArticleRevision']"}), - 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), - 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'other_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'other_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'owned_articles'", 'null': 'True', 'to': "orm['auth.User']"}) - }, - 'wiki.articleforobject': { - 'Meta': {'unique_together': "(('content_type', 'object_id'),)", 'object_name': 'ArticleForObject'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_type_set_for_articleforobject'", 'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_mptt': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}) - }, - 'wiki.articleplugin': { - 'Meta': {'object_name': 'ArticlePlugin'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'wiki.articlerevision': { - 'Meta': {'ordering': "('created',)", 'unique_together': "(('article', 'revision_number'),)", 'object_name': 'ArticleRevision'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']", 'null': 'True', 'blank': 'True'}), - 'redirect': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'redirect_set'", 'null': 'True', 'to': "orm['wiki.Article']"}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '512'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.attachment': { - 'Meta': {'object_name': 'Attachment', '_ormbases': ['wiki.ReusablePlugin']}, - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.AttachmentRevision']"}), - 'original_filename': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}), - 'reusableplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ReusablePlugin']", 'unique': 'True', 'primary_key': 'True'}) - }, - 'wiki.attachmentrevision': { - 'Meta': {'ordering': "('created',)", 'object_name': 'AttachmentRevision'}, - 'attachment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Attachment']"}), - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.AttachmentRevision']", 'null': 'True', 'blank': 'True'}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.image': { - 'Meta': {'object_name': 'Image', '_ormbases': ['wiki.RevisionPlugin']}, - 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '2000'}), - 'revisionplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.RevisionPlugin']", 'unique': 'True', 'primary_key': 'True'}) - }, - 'wiki.reusableplugin': { - 'Meta': {'object_name': 'ReusablePlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'articles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'shared_plugins_set'", 'symmetrical': 'False', 'to': "orm['wiki.Article']"}) - }, - 'wiki.revisionplugin': { - 'Meta': {'object_name': 'RevisionPlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']"}) - }, - 'wiki.urlpath': { - 'Meta': {'unique_together': "(('site', 'parent', 'slug'),)", 'object_name': 'URLPath'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'parent': ('mptt.fields.TreeForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['wiki.URLPath']"}), - 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), - 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}) - } - } - - complete_apps = ['wiki'] \ No newline at end of file diff --git a/wiki/migrations/0007_auto__add_articlesubscription.py b/wiki/migrations/0007_auto__add_articlesubscription.py deleted file mode 100644 index 0576705b2..000000000 --- a/wiki/migrations/0007_auto__add_articlesubscription.py +++ /dev/null @@ -1,190 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding model 'ArticleSubscription' - db.create_table('wiki_articlesubscription', ( - ('subscription_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['django_notify.Subscription'], unique=True)), - ('articleplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['wiki.ArticlePlugin'], unique=True, primary_key=True)), - )) - db.send_create_signal('wiki', ['ArticleSubscription']) - - - def backwards(self, orm): - # Deleting model 'ArticleSubscription' - db.delete_table('wiki_articlesubscription') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'django_notify.notificationtype': { - 'Meta': {'object_name': 'NotificationType', 'db_table': "'notify_notificationtype'"}, - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}), - 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128', 'primary_key': 'True'}), - 'label': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}) - }, - 'django_notify.settings': { - 'Meta': {'object_name': 'Settings', 'db_table': "'notify_settings'"}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'interval': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'django_notify.subscription': { - 'Meta': {'object_name': 'Subscription', 'db_table': "'notify_subscription'"}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'notification_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_notify.NotificationType']"}), - 'object_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), - 'send_emails': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'settings': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_notify.Settings']"}) - }, - 'sites.site': { - 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, - 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'wiki.article': { - 'Meta': {'object_name': 'Article'}, - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.ArticleRevision']"}), - 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), - 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'other_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'other_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'owned_articles'", 'null': 'True', 'to': "orm['auth.User']"}) - }, - 'wiki.articleforobject': { - 'Meta': {'unique_together': "(('content_type', 'object_id'),)", 'object_name': 'ArticleForObject'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_type_set_for_articleforobject'", 'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_mptt': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}) - }, - 'wiki.articleplugin': { - 'Meta': {'object_name': 'ArticlePlugin'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'wiki.articlerevision': { - 'Meta': {'ordering': "('created',)", 'unique_together': "(('article', 'revision_number'),)", 'object_name': 'ArticleRevision'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']", 'null': 'True', 'blank': 'True'}), - 'redirect': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'redirect_set'", 'null': 'True', 'to': "orm['wiki.Article']"}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '512'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.articlesubscription': { - 'Meta': {'object_name': 'ArticleSubscription', '_ormbases': ['wiki.ArticlePlugin', 'django_notify.Subscription']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'subscription_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['django_notify.Subscription']", 'unique': 'True'}) - }, - 'wiki.attachment': { - 'Meta': {'object_name': 'Attachment', '_ormbases': ['wiki.ReusablePlugin']}, - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.AttachmentRevision']"}), - 'original_filename': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}), - 'reusableplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ReusablePlugin']", 'unique': 'True', 'primary_key': 'True'}) - }, - 'wiki.attachmentrevision': { - 'Meta': {'ordering': "('created',)", 'object_name': 'AttachmentRevision'}, - 'attachment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Attachment']"}), - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.AttachmentRevision']", 'null': 'True', 'blank': 'True'}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.image': { - 'Meta': {'object_name': 'Image', '_ormbases': ['wiki.RevisionPlugin']}, - 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '2000'}), - 'revisionplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.RevisionPlugin']", 'unique': 'True', 'primary_key': 'True'}) - }, - 'wiki.reusableplugin': { - 'Meta': {'object_name': 'ReusablePlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'articles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'shared_plugins_set'", 'symmetrical': 'False', 'to': "orm['wiki.Article']"}) - }, - 'wiki.revisionplugin': { - 'Meta': {'object_name': 'RevisionPlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']"}) - }, - 'wiki.urlpath': { - 'Meta': {'unique_together': "(('site', 'parent', 'slug'),)", 'object_name': 'URLPath'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'parent': ('mptt.fields.TreeForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['wiki.URLPath']"}), - 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), - 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}) - } - } - - complete_apps = ['wiki'] \ No newline at end of file diff --git a/wiki/migrations/0008_auto__add_simpleplugin__add_revisionpluginrevision__add_imagerevision_.py b/wiki/migrations/0008_auto__add_simpleplugin__add_revisionpluginrevision__add_imagerevision_.py deleted file mode 100644 index cef047bd4..000000000 --- a/wiki/migrations/0008_auto__add_simpleplugin__add_revisionpluginrevision__add_imagerevision_.py +++ /dev/null @@ -1,271 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding model 'SimplePlugin' - db.create_table('wiki_simpleplugin', ( - ('articleplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['wiki.ArticlePlugin'], unique=True, primary_key=True)), - ('article_revision', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['wiki.ArticleRevision'])), - )) - db.send_create_signal('wiki', ['SimplePlugin']) - - # Adding model 'RevisionPluginRevision' - db.create_table('wiki_revisionpluginrevision', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('revision_number', self.gf('django.db.models.fields.IntegerField')()), - ('user_message', self.gf('django.db.models.fields.TextField')(blank=True)), - ('automatic_log', self.gf('django.db.models.fields.TextField')(blank=True)), - ('ip_address', self.gf('django.db.models.fields.IPAddressField')(max_length=15, null=True, blank=True)), - ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)), - ('modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), - ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), - ('previous_revision', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['wiki.RevisionPluginRevision'], null=True, blank=True)), - ('deleted', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('locked', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('plugin', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['wiki.RevisionPlugin'])), - )) - db.send_create_signal('wiki', ['RevisionPluginRevision']) - - # Adding model 'ImageRevision' - db.create_table('wiki_imagerevision', ( - ('revisionpluginrevision_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['wiki.RevisionPluginRevision'], unique=True, primary_key=True)), - ('image', self.gf('django.db.models.fields.files.ImageField')(max_length=2000)), - )) - db.send_create_signal('wiki', ['ImageRevision']) - - # Deleting field 'Image.image' - db.delete_column('wiki_image', 'image') - - # Deleting field 'ArticleRevision.redirect' - db.delete_column('wiki_articlerevision', 'redirect_id') - - # Deleting field 'RevisionPlugin.revision' - db.delete_column('wiki_revisionplugin', 'revision_id') - - # Adding field 'RevisionPlugin.current_revision' - db.add_column('wiki_revisionplugin', 'current_revision', - self.gf('django.db.models.fields.related.OneToOneField')(blank=True, related_name='plugin_set', unique=True, null=True, to=orm['wiki.RevisionPluginRevision']), - keep_default=False) - - - def backwards(self, orm): - # Deleting model 'SimplePlugin' - db.delete_table('wiki_simpleplugin') - - # Deleting model 'RevisionPluginRevision' - db.delete_table('wiki_revisionpluginrevision') - - # Deleting model 'ImageRevision' - db.delete_table('wiki_imagerevision') - - - # User chose to not deal with backwards NULL issues for 'Image.image' - raise RuntimeError("Cannot reverse this migration. 'Image.image' and its values cannot be restored.") - # Adding field 'ArticleRevision.redirect' - db.add_column('wiki_articlerevision', 'redirect', - self.gf('django.db.models.fields.related.ForeignKey')(related_name='redirect_set', null=True, to=orm['wiki.Article'], blank=True), - keep_default=False) - - - # User chose to not deal with backwards NULL issues for 'RevisionPlugin.revision' - raise RuntimeError("Cannot reverse this migration. 'RevisionPlugin.revision' and its values cannot be restored.") - # Deleting field 'RevisionPlugin.current_revision' - db.delete_column('wiki_revisionplugin', 'current_revision_id') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'django_notify.notificationtype': { - 'Meta': {'object_name': 'NotificationType', 'db_table': "'notify_notificationtype'"}, - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}), - 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128', 'primary_key': 'True'}), - 'label': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}) - }, - 'django_notify.settings': { - 'Meta': {'object_name': 'Settings', 'db_table': "'notify_settings'"}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'interval': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'django_notify.subscription': { - 'Meta': {'object_name': 'Subscription', 'db_table': "'notify_subscription'"}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'notification_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_notify.NotificationType']"}), - 'object_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), - 'send_emails': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'settings': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_notify.Settings']"}) - }, - 'sites.site': { - 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, - 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'wiki.article': { - 'Meta': {'object_name': 'Article'}, - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.ArticleRevision']"}), - 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), - 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'other_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'other_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'owned_articles'", 'null': 'True', 'to': "orm['auth.User']"}) - }, - 'wiki.articleforobject': { - 'Meta': {'unique_together': "(('content_type', 'object_id'),)", 'object_name': 'ArticleForObject'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_type_set_for_articleforobject'", 'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_mptt': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}) - }, - 'wiki.articleplugin': { - 'Meta': {'object_name': 'ArticlePlugin'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'wiki.articlerevision': { - 'Meta': {'ordering': "('created',)", 'unique_together': "(('article', 'revision_number'),)", 'object_name': 'ArticleRevision'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']", 'null': 'True', 'blank': 'True'}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '512'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.articlesubscription': { - 'Meta': {'object_name': 'ArticleSubscription', '_ormbases': ['wiki.ArticlePlugin', 'django_notify.Subscription']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'subscription_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['django_notify.Subscription']", 'unique': 'True'}) - }, - 'wiki.attachment': { - 'Meta': {'object_name': 'Attachment', '_ormbases': ['wiki.ReusablePlugin']}, - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.AttachmentRevision']"}), - 'original_filename': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}), - 'reusableplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ReusablePlugin']", 'unique': 'True', 'primary_key': 'True'}) - }, - 'wiki.attachmentrevision': { - 'Meta': {'ordering': "('created',)", 'object_name': 'AttachmentRevision'}, - 'attachment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Attachment']"}), - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.AttachmentRevision']", 'null': 'True', 'blank': 'True'}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.image': { - 'Meta': {'object_name': 'Image', '_ormbases': ['wiki.RevisionPlugin']}, - 'revisionplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.RevisionPlugin']", 'unique': 'True', 'primary_key': 'True'}) - }, - 'wiki.imagerevision': { - 'Meta': {'object_name': 'ImageRevision', '_ormbases': ['wiki.RevisionPluginRevision']}, - 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '2000'}), - 'revisionpluginrevision_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.RevisionPluginRevision']", 'unique': 'True', 'primary_key': 'True'}) - }, - 'wiki.reusableplugin': { - 'Meta': {'object_name': 'ReusablePlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'articles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'shared_plugins_set'", 'symmetrical': 'False', 'to': "orm['wiki.Article']"}) - }, - 'wiki.revisionplugin': { - 'Meta': {'object_name': 'RevisionPlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'plugin_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.RevisionPluginRevision']"}) - }, - 'wiki.revisionpluginrevision': { - 'Meta': {'object_name': 'RevisionPluginRevision'}, - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'plugin': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.RevisionPlugin']"}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.RevisionPluginRevision']", 'null': 'True', 'blank': 'True'}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.simpleplugin': { - 'Meta': {'object_name': 'SimplePlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'article_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']"}), - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}) - }, - 'wiki.urlpath': { - 'Meta': {'unique_together': "(('site', 'parent', 'slug'),)", 'object_name': 'URLPath'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'parent': ('mptt.fields.TreeForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['wiki.URLPath']"}), - 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), - 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}) - } - } - - complete_apps = ['wiki'] \ No newline at end of file diff --git a/wiki/migrations/0009_auto__add_field_imagerevision_width__add_field_imagerevision_height.py b/wiki/migrations/0009_auto__add_field_imagerevision_width__add_field_imagerevision_height.py deleted file mode 100644 index d5b9c74d0..000000000 --- a/wiki/migrations/0009_auto__add_field_imagerevision_width__add_field_imagerevision_height.py +++ /dev/null @@ -1,221 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding field 'ImageRevision.width' - db.add_column('wiki_imagerevision', 'width', - self.gf('django.db.models.fields.SmallIntegerField')(default=0), - keep_default=False) - - # Adding field 'ImageRevision.height' - db.add_column('wiki_imagerevision', 'height', - self.gf('django.db.models.fields.SmallIntegerField')(default=0), - keep_default=False) - - - def backwards(self, orm): - # Deleting field 'ImageRevision.width' - db.delete_column('wiki_imagerevision', 'width') - - # Deleting field 'ImageRevision.height' - db.delete_column('wiki_imagerevision', 'height') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'django_notify.notificationtype': { - 'Meta': {'object_name': 'NotificationType', 'db_table': "'notify_notificationtype'"}, - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}), - 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128', 'primary_key': 'True'}), - 'label': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}) - }, - 'django_notify.settings': { - 'Meta': {'object_name': 'Settings', 'db_table': "'notify_settings'"}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'interval': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'django_notify.subscription': { - 'Meta': {'object_name': 'Subscription', 'db_table': "'notify_subscription'"}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'notification_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_notify.NotificationType']"}), - 'object_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), - 'send_emails': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'settings': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_notify.Settings']"}) - }, - 'sites.site': { - 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, - 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'wiki.article': { - 'Meta': {'object_name': 'Article'}, - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.ArticleRevision']"}), - 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), - 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'other_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'other_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'owned_articles'", 'null': 'True', 'to': "orm['auth.User']"}) - }, - 'wiki.articleforobject': { - 'Meta': {'unique_together': "(('content_type', 'object_id'),)", 'object_name': 'ArticleForObject'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_type_set_for_articleforobject'", 'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_mptt': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}) - }, - 'wiki.articleplugin': { - 'Meta': {'object_name': 'ArticlePlugin'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'wiki.articlerevision': { - 'Meta': {'ordering': "('created',)", 'unique_together': "(('article', 'revision_number'),)", 'object_name': 'ArticleRevision'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']", 'null': 'True', 'blank': 'True'}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '512'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.articlesubscription': { - 'Meta': {'object_name': 'ArticleSubscription', '_ormbases': ['wiki.ArticlePlugin', 'django_notify.Subscription']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'subscription_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['django_notify.Subscription']", 'unique': 'True'}) - }, - 'wiki.attachment': { - 'Meta': {'object_name': 'Attachment', '_ormbases': ['wiki.ReusablePlugin']}, - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.AttachmentRevision']"}), - 'original_filename': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}), - 'reusableplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ReusablePlugin']", 'unique': 'True', 'primary_key': 'True'}) - }, - 'wiki.attachmentrevision': { - 'Meta': {'ordering': "('created',)", 'object_name': 'AttachmentRevision'}, - 'attachment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Attachment']"}), - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.AttachmentRevision']", 'null': 'True', 'blank': 'True'}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.image': { - 'Meta': {'object_name': 'Image', '_ormbases': ['wiki.RevisionPlugin']}, - 'revisionplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.RevisionPlugin']", 'unique': 'True', 'primary_key': 'True'}) - }, - 'wiki.imagerevision': { - 'Meta': {'object_name': 'ImageRevision', '_ormbases': ['wiki.RevisionPluginRevision']}, - 'height': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), - 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '2000'}), - 'revisionpluginrevision_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.RevisionPluginRevision']", 'unique': 'True', 'primary_key': 'True'}), - 'width': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}) - }, - 'wiki.reusableplugin': { - 'Meta': {'object_name': 'ReusablePlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'articles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'shared_plugins_set'", 'symmetrical': 'False', 'to': "orm['wiki.Article']"}) - }, - 'wiki.revisionplugin': { - 'Meta': {'object_name': 'RevisionPlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'plugin_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.RevisionPluginRevision']"}) - }, - 'wiki.revisionpluginrevision': { - 'Meta': {'object_name': 'RevisionPluginRevision'}, - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'plugin': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revision_set'", 'to': "orm['wiki.RevisionPlugin']"}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.RevisionPluginRevision']", 'null': 'True', 'blank': 'True'}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.simpleplugin': { - 'Meta': {'object_name': 'SimplePlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'article_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']"}), - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}) - }, - 'wiki.urlpath': { - 'Meta': {'unique_together': "(('site', 'parent', 'slug'),)", 'object_name': 'URLPath'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'parent': ('mptt.fields.TreeForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['wiki.URLPath']"}), - 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), - 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}) - } - } - - complete_apps = ['wiki'] \ No newline at end of file diff --git a/wiki/migrations/0010_auto__chg_field_imagerevision_image.py b/wiki/migrations/0010_auto__chg_field_imagerevision_image.py deleted file mode 100644 index ac1510d9e..000000000 --- a/wiki/migrations/0010_auto__chg_field_imagerevision_image.py +++ /dev/null @@ -1,211 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - - # Changing field 'ImageRevision.image' - db.alter_column('wiki_imagerevision', 'image', self.gf('django.db.models.fields.files.ImageField')(max_length=2000, null=True)) - - def backwards(self, orm): - - # User chose to not deal with backwards NULL issues for 'ImageRevision.image' - raise RuntimeError("Cannot reverse this migration. 'ImageRevision.image' and its values cannot be restored.") - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'django_notify.notificationtype': { - 'Meta': {'object_name': 'NotificationType', 'db_table': "'notify_notificationtype'"}, - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}), - 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128', 'primary_key': 'True'}), - 'label': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}) - }, - 'django_notify.settings': { - 'Meta': {'object_name': 'Settings', 'db_table': "'notify_settings'"}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'interval': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'django_notify.subscription': { - 'Meta': {'object_name': 'Subscription', 'db_table': "'notify_subscription'"}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'notification_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_notify.NotificationType']"}), - 'object_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), - 'send_emails': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'settings': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_notify.Settings']"}) - }, - 'sites.site': { - 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, - 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'wiki.article': { - 'Meta': {'object_name': 'Article'}, - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.ArticleRevision']"}), - 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), - 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'other_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'other_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'owned_articles'", 'null': 'True', 'to': "orm['auth.User']"}) - }, - 'wiki.articleforobject': { - 'Meta': {'unique_together': "(('content_type', 'object_id'),)", 'object_name': 'ArticleForObject'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_type_set_for_articleforobject'", 'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_mptt': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}) - }, - 'wiki.articleplugin': { - 'Meta': {'object_name': 'ArticlePlugin'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'wiki.articlerevision': { - 'Meta': {'ordering': "('created',)", 'unique_together': "(('article', 'revision_number'),)", 'object_name': 'ArticleRevision'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']", 'null': 'True', 'blank': 'True'}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '512'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.articlesubscription': { - 'Meta': {'object_name': 'ArticleSubscription', '_ormbases': ['wiki.ArticlePlugin', 'django_notify.Subscription']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'subscription_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['django_notify.Subscription']", 'unique': 'True'}) - }, - 'wiki.attachment': { - 'Meta': {'object_name': 'Attachment', '_ormbases': ['wiki.ReusablePlugin']}, - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.AttachmentRevision']"}), - 'original_filename': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}), - 'reusableplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ReusablePlugin']", 'unique': 'True', 'primary_key': 'True'}) - }, - 'wiki.attachmentrevision': { - 'Meta': {'ordering': "('created',)", 'object_name': 'AttachmentRevision'}, - 'attachment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Attachment']"}), - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.AttachmentRevision']", 'null': 'True', 'blank': 'True'}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.image': { - 'Meta': {'object_name': 'Image', '_ormbases': ['wiki.RevisionPlugin']}, - 'revisionplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.RevisionPlugin']", 'unique': 'True', 'primary_key': 'True'}) - }, - 'wiki.imagerevision': { - 'Meta': {'ordering': "('-created',)", 'object_name': 'ImageRevision', '_ormbases': ['wiki.RevisionPluginRevision']}, - 'height': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), - 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '2000', 'null': 'True', 'blank': 'True'}), - 'revisionpluginrevision_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.RevisionPluginRevision']", 'unique': 'True', 'primary_key': 'True'}), - 'width': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}) - }, - 'wiki.reusableplugin': { - 'Meta': {'object_name': 'ReusablePlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'articles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'shared_plugins_set'", 'symmetrical': 'False', 'to': "orm['wiki.Article']"}) - }, - 'wiki.revisionplugin': { - 'Meta': {'object_name': 'RevisionPlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'plugin_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.RevisionPluginRevision']"}) - }, - 'wiki.revisionpluginrevision': { - 'Meta': {'ordering': "('-created',)", 'object_name': 'RevisionPluginRevision'}, - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'plugin': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revision_set'", 'to': "orm['wiki.RevisionPlugin']"}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.RevisionPluginRevision']", 'null': 'True', 'blank': 'True'}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.simpleplugin': { - 'Meta': {'object_name': 'SimplePlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'article_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']"}), - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}) - }, - 'wiki.urlpath': { - 'Meta': {'unique_together': "(('site', 'parent', 'slug'),)", 'object_name': 'URLPath'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'parent': ('mptt.fields.TreeForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['wiki.URLPath']"}), - 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), - 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}) - } - } - - complete_apps = ['wiki'] \ No newline at end of file diff --git a/wiki/migrations/0011_auto__chg_field_imagerevision_width__chg_field_imagerevision_height.py b/wiki/migrations/0011_auto__chg_field_imagerevision_width__chg_field_imagerevision_height.py deleted file mode 100644 index 593db509f..000000000 --- a/wiki/migrations/0011_auto__chg_field_imagerevision_width__chg_field_imagerevision_height.py +++ /dev/null @@ -1,217 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - - # Changing field 'ImageRevision.width' - db.alter_column('wiki_imagerevision', 'width', self.gf('django.db.models.fields.SmallIntegerField')(null=True)) - - # Changing field 'ImageRevision.height' - db.alter_column('wiki_imagerevision', 'height', self.gf('django.db.models.fields.SmallIntegerField')(null=True)) - - def backwards(self, orm): - - # Changing field 'ImageRevision.width' - db.alter_column('wiki_imagerevision', 'width', self.gf('django.db.models.fields.SmallIntegerField')()) - - # Changing field 'ImageRevision.height' - db.alter_column('wiki_imagerevision', 'height', self.gf('django.db.models.fields.SmallIntegerField')()) - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'django_notify.notificationtype': { - 'Meta': {'object_name': 'NotificationType', 'db_table': "'notify_notificationtype'"}, - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}), - 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128', 'primary_key': 'True'}), - 'label': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}) - }, - 'django_notify.settings': { - 'Meta': {'object_name': 'Settings', 'db_table': "'notify_settings'"}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'interval': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'django_notify.subscription': { - 'Meta': {'object_name': 'Subscription', 'db_table': "'notify_subscription'"}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'notification_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_notify.NotificationType']"}), - 'object_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), - 'send_emails': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'settings': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_notify.Settings']"}) - }, - 'sites.site': { - 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, - 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'wiki.article': { - 'Meta': {'object_name': 'Article'}, - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.ArticleRevision']"}), - 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), - 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'other_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'other_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'owned_articles'", 'null': 'True', 'to': "orm['auth.User']"}) - }, - 'wiki.articleforobject': { - 'Meta': {'unique_together': "(('content_type', 'object_id'),)", 'object_name': 'ArticleForObject'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_type_set_for_articleforobject'", 'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_mptt': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}) - }, - 'wiki.articleplugin': { - 'Meta': {'object_name': 'ArticlePlugin'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'wiki.articlerevision': { - 'Meta': {'ordering': "('created',)", 'unique_together': "(('article', 'revision_number'),)", 'object_name': 'ArticleRevision'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']", 'null': 'True', 'blank': 'True'}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '512'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.articlesubscription': { - 'Meta': {'object_name': 'ArticleSubscription', '_ormbases': ['wiki.ArticlePlugin', 'django_notify.Subscription']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'subscription_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['django_notify.Subscription']", 'unique': 'True'}) - }, - 'wiki.attachment': { - 'Meta': {'object_name': 'Attachment', '_ormbases': ['wiki.ReusablePlugin']}, - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.AttachmentRevision']"}), - 'original_filename': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}), - 'reusableplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ReusablePlugin']", 'unique': 'True', 'primary_key': 'True'}) - }, - 'wiki.attachmentrevision': { - 'Meta': {'ordering': "('created',)", 'object_name': 'AttachmentRevision'}, - 'attachment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Attachment']"}), - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.AttachmentRevision']", 'null': 'True', 'blank': 'True'}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.image': { - 'Meta': {'object_name': 'Image', '_ormbases': ['wiki.RevisionPlugin']}, - 'revisionplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.RevisionPlugin']", 'unique': 'True', 'primary_key': 'True'}) - }, - 'wiki.imagerevision': { - 'Meta': {'ordering': "('-created',)", 'object_name': 'ImageRevision', '_ormbases': ['wiki.RevisionPluginRevision']}, - 'height': ('django.db.models.fields.SmallIntegerField', [], {'null': 'True', 'blank': 'True'}), - 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '2000', 'null': 'True', 'blank': 'True'}), - 'revisionpluginrevision_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.RevisionPluginRevision']", 'unique': 'True', 'primary_key': 'True'}), - 'width': ('django.db.models.fields.SmallIntegerField', [], {'null': 'True', 'blank': 'True'}) - }, - 'wiki.reusableplugin': { - 'Meta': {'object_name': 'ReusablePlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'articles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'shared_plugins_set'", 'symmetrical': 'False', 'to': "orm['wiki.Article']"}) - }, - 'wiki.revisionplugin': { - 'Meta': {'object_name': 'RevisionPlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'plugin_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.RevisionPluginRevision']"}) - }, - 'wiki.revisionpluginrevision': { - 'Meta': {'ordering': "('-created',)", 'object_name': 'RevisionPluginRevision'}, - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'plugin': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revision_set'", 'to': "orm['wiki.RevisionPlugin']"}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.RevisionPluginRevision']", 'null': 'True', 'blank': 'True'}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.simpleplugin': { - 'Meta': {'object_name': 'SimplePlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'article_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']"}), - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}) - }, - 'wiki.urlpath': { - 'Meta': {'unique_together': "(('site', 'parent', 'slug'),)", 'object_name': 'URLPath'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'parent': ('mptt.fields.TreeForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['wiki.URLPath']"}), - 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), - 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), - 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}) - } - } - - complete_apps = ['wiki'] \ No newline at end of file diff --git a/wiki/models/__init__.py b/wiki/models/__init__.py index da2592988..0a5b1c3dd 100644 --- a/wiki/models/__init__.py +++ b/wiki/models/__init__.py @@ -32,13 +32,6 @@ if not 'django.contrib.auth.context_processors.auth' in django_settings.TEMPLATE_CONTEXT_PROCESSORS: raise ImproperlyConfigured('django-wiki: needs django.contrib.auth.context_processors.auth in TEMPLATE_CONTEXT_PROCESSORS') -###################### -# Warnings -###################### - -if not 'south' in django_settings.INSTALLED_APPS: - warnings.warn("django-wiki: No south in your INSTALLED_APPS. This is highly discouraged.") - ###################### # PLUGINS diff --git a/wiki/models/article.py b/wiki/models/article.py index 8e56bc4a0..064dad4fb 100644 --- a/wiki/models/article.py +++ b/wiki/models/article.py @@ -229,8 +229,8 @@ class BaseRevisionMixin(models.Model): # NOTE! The semantics of these fields are not related to the revision itself # but the actual related object. If the latest revision says "deleted=True" then # the related object should be regarded as deleted. - deleted = models.BooleanField(verbose_name=_(u'deleted')) - locked = models.BooleanField(verbose_name=_(u'locked')) + deleted = models.BooleanField(verbose_name=_(u'deleted'), default=False) + locked = models.BooleanField(verbose_name=_(u'locked'), default=False) def set_from_request(self, request): if request.user.is_authenticated(): @@ -306,7 +306,7 @@ def save(self, *args, **kwargs): class Meta: app_label = settings.APP_LABEL - get_latest_by = ('revision_number',) + get_latest_by = 'revision_number' ordering = ('created',) unique_together = ('article', 'revision_number') diff --git a/wiki/models/pluginbase.py b/wiki/models/pluginbase.py index 4928f39f4..887384ba2 100644 --- a/wiki/models/pluginbase.py +++ b/wiki/models/pluginbase.py @@ -227,7 +227,7 @@ def save(self, *args, **kwargs): class Meta: app_label = settings.APP_LABEL - get_latest_by = ('revision_number',) + get_latest_by = 'revision_number' ordering = ('-created',) ###################################################### diff --git a/wiki/models/urlpath.py b/wiki/models/urlpath.py index ac53dfe52..f0a51a587 100644 --- a/wiki/models/urlpath.py +++ b/wiki/models/urlpath.py @@ -93,20 +93,17 @@ def first_deleted_ancestor(self): return ancestor return None - @transaction.commit_manually def delete_subtree(self): """ NB! This deletes this urlpath, its children, and ALL of the related articles. This is a purged delete and CANNOT be undone. """ try: - for descendant in self.get_descendants(include_self=True).order_by("-level"): - print "deleting " , descendant - descendant.article.delete() - - transaction.commit() + with transaction.atomic(): + for descendant in self.get_descendants(include_self=True).order_by("-level"): + print "deleting " , descendant + descendant.article.delete() except: - transaction.rollback() log.exception("Exception deleting article subtree.") diff --git a/wiki/plugins/attachments/migrations/0001_initial.py b/wiki/plugins/attachments/migrations/0001_initial.py deleted file mode 100644 index 349fd019f..000000000 --- a/wiki/plugins/attachments/migrations/0001_initial.py +++ /dev/null @@ -1,151 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding model 'Attachment' - db.create_table('attachments_attachment', ( - ('reusableplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['wiki.ReusablePlugin'], unique=True, primary_key=True)), - ('current_revision', self.gf('django.db.models.fields.related.OneToOneField')(blank=True, related_name='current_set', unique=True, null=True, to=orm['attachments.AttachmentRevision'])), - ('original_filename', self.gf('django.db.models.fields.CharField')(max_length=256, null=True, blank=True)), - )) - db.send_create_signal('attachments', ['Attachment']) - - # Adding model 'AttachmentRevision' - db.create_table('attachments_attachmentrevision', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('revision_number', self.gf('django.db.models.fields.IntegerField')()), - ('user_message', self.gf('django.db.models.fields.TextField')(blank=True)), - ('automatic_log', self.gf('django.db.models.fields.TextField')(blank=True)), - ('ip_address', self.gf('django.db.models.fields.IPAddressField')(max_length=15, null=True, blank=True)), - ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)), - ('modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), - ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), - ('previous_revision', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['attachments.AttachmentRevision'], null=True, blank=True)), - ('deleted', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('locked', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('attachment', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['attachments.Attachment'])), - ('file', self.gf('django.db.models.fields.files.FileField')(max_length=100)), - ('description', self.gf('django.db.models.fields.TextField')(blank=True)), - )) - db.send_create_signal('attachments', ['AttachmentRevision']) - - - def backwards(self, orm): - # Deleting model 'Attachment' - db.delete_table('attachments_attachment') - - # Deleting model 'AttachmentRevision' - db.delete_table('attachments_attachmentrevision') - - - models = { - 'attachments.attachment': { - 'Meta': {'object_name': 'Attachment', '_ormbases': ['wiki.ReusablePlugin']}, - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['attachments.AttachmentRevision']"}), - 'original_filename': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}), - 'reusableplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ReusablePlugin']", 'unique': 'True', 'primary_key': 'True'}) - }, - 'attachments.attachmentrevision': { - 'Meta': {'ordering': "('created',)", 'object_name': 'AttachmentRevision'}, - 'attachment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['attachments.Attachment']"}), - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['attachments.AttachmentRevision']", 'null': 'True', 'blank': 'True'}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'wiki.article': { - 'Meta': {'object_name': 'Article'}, - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.ArticleRevision']"}), - 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), - 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'other_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'other_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) - }, - 'wiki.articleplugin': { - 'Meta': {'object_name': 'ArticlePlugin'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'wiki.articlerevision': { - 'Meta': {'ordering': "('created',)", 'unique_together': "(('article', 'revision_number'),)", 'object_name': 'ArticleRevision'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']", 'null': 'True', 'blank': 'True'}), - 'redirect': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'redirect_set'", 'null': 'True', 'to': "orm['wiki.Article']"}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '512'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.reusableplugin': { - 'Meta': {'object_name': 'ReusablePlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'articles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'shared_plugins_set'", 'symmetrical': 'False', 'to': "orm['wiki.Article']"}) - } - } - - complete_apps = ['attachments'] \ No newline at end of file diff --git a/wiki/plugins/attachments/models.py b/wiki/plugins/attachments/models.py index 427b92bc1..8a903894e 100644 --- a/wiki/plugins/attachments/models.py +++ b/wiki/plugins/attachments/models.py @@ -85,7 +85,7 @@ class Meta: verbose_name = _(u'attachment revision') verbose_name_plural = _(u'attachment revisions') ordering = ('created',) - get_latest_by = ('revision_number',) + get_latest_by = 'revision_number' app_label = settings.APP_LABEL def get_filename(self): diff --git a/wiki/plugins/attachments/views.py b/wiki/plugins/attachments/views.py index 2a5fe507f..5c7b7fa13 100644 --- a/wiki/plugins/attachments/views.py +++ b/wiki/plugins/attachments/views.py @@ -31,32 +31,27 @@ def dispatch(self, request, article, *args, **kwargs): # Fixing some weird transaction issue caused by adding commit_manually to form_valid return super(AttachmentView, self).dispatch(request, article, *args, **kwargs) - # WARNING! The below decorator silences other exceptions that may occur! - @transaction.commit_manually def form_valid(self, form): if (self.request.user.is_anonymous() and not settings.ANONYMOUS or not self.article.can_write(self.request.user)): return response_forbidden(self.request, self.article, self.urlpath) - try: - attachment_revision = form.save(commit=False) - attachment = models.Attachment() - attachment.article = self.article - attachment.original_filename = attachment_revision.get_filename() - attachment.save() - attachment.articles.add(self.article) - attachment_revision.attachment = attachment - attachment_revision.set_from_request(self.request) - attachment_revision.save() - messages.success(self.request, _(u'%s was successfully added.') % attachment_revision.get_filename()) + with transaction.atomic(): + attachment_revision = form.save(commit=False) + attachment = models.Attachment() + attachment.article = self.article + attachment.original_filename = attachment_revision.get_filename() + attachment.save() + attachment.articles.add(self.article) + attachment_revision.attachment = attachment + attachment_revision.set_from_request(self.request) + attachment_revision.save() + messages.success(self.request, _(u'%s was successfully added.') % attachment_revision.get_filename()) except models.IllegalFileExtension, e: - transaction.rollback() messages.error(self.request, _(u'Your file could not be saved: %s') % e) except Exception: - transaction.rollback() messages.error(self.request, _(u'Your file could not be saved, probably because of a permission error on the web server.')) - transaction.commit() return redirect("wiki:attachments_index", path=self.urlpath.path, article_id=self.article.id) def get_context_data(self, **kwargs): diff --git a/wiki/plugins/attachments/wiki_plugin.py b/wiki/plugins/attachments/wiki_plugin.py index 47c51fce8..28f883f19 100644 --- a/wiki/plugins/attachments/wiki_plugin.py +++ b/wiki/plugins/attachments/wiki_plugin.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from django.conf.urls.defaults import patterns, url +from django.conf.urls import patterns, url from django.utils.translation import ugettext_lazy as _ from wiki.core.plugins import registry diff --git a/wiki/plugins/help/wiki_plugin.py b/wiki/plugins/help/wiki_plugin.py index f2b9a6dde..15ebd1e6a 100644 --- a/wiki/plugins/help/wiki_plugin.py +++ b/wiki/plugins/help/wiki_plugin.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from django.conf.urls.defaults import patterns +from django.conf.urls import patterns from django.utils.translation import ugettext_lazy as _ from wiki.core.plugins import registry diff --git a/wiki/plugins/images/admin.py b/wiki/plugins/images/admin.py index 9683678e2..534bbbfc4 100644 --- a/wiki/plugins/images/admin.py +++ b/wiki/plugins/images/admin.py @@ -6,6 +6,7 @@ class ImageForm(forms.ModelForm): class Meta: model = models.Image + fields = '__all__' def __init__(self, *args, **kwargs): super(ImageForm, self).__init__(*args, **kwargs) diff --git a/wiki/plugins/images/migrations/0001_initial.py b/wiki/plugins/images/migrations/0001_initial.py deleted file mode 100644 index 609a2db7d..000000000 --- a/wiki/plugins/images/migrations/0001_initial.py +++ /dev/null @@ -1,111 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding model 'Image' - db.create_table('images_image', ( - ('revisionplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['wiki.RevisionPlugin'], unique=True, primary_key=True)), - ('image', self.gf('django.db.models.fields.files.ImageField')(max_length=100)), - ('caption', self.gf('django.db.models.fields.CharField')(max_length=2056, null=True, blank=True)), - )) - db.send_create_signal('images', ['Image']) - - - def backwards(self, orm): - # Deleting model 'Image' - db.delete_table('images_image') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'images.image': { - 'Meta': {'object_name': 'Image', '_ormbases': ['wiki.RevisionPlugin']}, - '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'}) - }, - 'wiki.article': { - 'Meta': {'object_name': 'Article'}, - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.ArticleRevision']"}), - 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), - 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'other_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'other_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) - }, - 'wiki.articleplugin': { - 'Meta': {'object_name': 'ArticlePlugin'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'wiki.articlerevision': { - 'Meta': {'ordering': "('created',)", 'unique_together': "(('article', 'revision_number'),)", 'object_name': 'ArticleRevision'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']", 'null': 'True', 'blank': 'True'}), - 'redirect': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'redirect_set'", 'null': 'True', 'to': "orm['wiki.Article']"}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '512'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - }, - 'wiki.revisionplugin': { - 'Meta': {'object_name': 'RevisionPlugin', '_ormbases': ['wiki.ArticlePlugin']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']"}) - } - } - - complete_apps = ['images'] \ No newline at end of file diff --git a/wiki/plugins/images/wiki_plugin.py b/wiki/plugins/images/wiki_plugin.py index 96bcd0abd..785ea51eb 100644 --- a/wiki/plugins/images/wiki_plugin.py +++ b/wiki/plugins/images/wiki_plugin.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from django.conf.urls.defaults import patterns, url +from django.conf.urls import patterns, url from django.utils.translation import ugettext_lazy as _ from wiki.core.plugins import registry diff --git a/wiki/plugins/links/wiki_plugin.py b/wiki/plugins/links/wiki_plugin.py index 4b6f72bdb..cc0c1245b 100644 --- a/wiki/plugins/links/wiki_plugin.py +++ b/wiki/plugins/links/wiki_plugin.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from django.conf.urls.defaults import patterns, url +from django.conf.urls import patterns, url from django.utils.translation import ugettext_lazy as _ from wiki.conf import settings diff --git a/wiki/plugins/notifications/migrations/0001_initial.py b/wiki/plugins/notifications/migrations/0001_initial.py deleted file mode 100644 index 57a06ba53..000000000 --- a/wiki/plugins/notifications/migrations/0001_initial.py +++ /dev/null @@ -1,125 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding model 'ArticleSubscription' - db.create_table('notifications_articlesubscription', ( - ('subscription_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['django_notify.Subscription'], unique=True)), - ('articleplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['wiki.ArticlePlugin'], unique=True, primary_key=True)), - )) - db.send_create_signal('notifications', ['ArticleSubscription']) - - - def backwards(self, orm): - # Deleting model 'ArticleSubscription' - db.delete_table('notifications_articlesubscription') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'django_notify.notificationtype': { - 'Meta': {'object_name': 'NotificationType', 'db_table': "'notify_notificationtype'"}, - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}), - 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128', 'primary_key': 'True'}), - 'label': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}) - }, - 'django_notify.settings': { - 'Meta': {'object_name': 'Settings', 'db_table': "'notify_settings'"}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'interval': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'django_notify.subscription': { - 'Meta': {'object_name': 'Subscription', 'db_table': "'notify_subscription'"}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'notification_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_notify.NotificationType']"}), - 'object_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), - 'send_emails': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'settings': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['django_notify.Settings']"}) - }, - 'notifications.articlesubscription': { - 'Meta': {'object_name': 'ArticleSubscription', '_ormbases': ['wiki.ArticlePlugin', 'django_notify.Subscription']}, - 'articleplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['wiki.ArticlePlugin']", 'unique': 'True', 'primary_key': 'True'}), - 'subscription_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['django_notify.Subscription']", 'unique': 'True'}) - }, - 'wiki.article': { - 'Meta': {'object_name': 'Article'}, - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_set'", 'unique': 'True', 'null': 'True', 'to': "orm['wiki.ArticleRevision']"}), - 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), - 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'other_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'other_write': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) - }, - 'wiki.articleplugin': { - 'Meta': {'object_name': 'ArticlePlugin'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'wiki.articlerevision': { - 'Meta': {'ordering': "('created',)", 'unique_together': "(('article', 'revision_number'),)", 'object_name': 'ArticleRevision'}, - 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.Article']"}), - 'automatic_log': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), - 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), - 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['wiki.ArticleRevision']", 'null': 'True', 'blank': 'True'}), - 'redirect': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'redirect_set'", 'null': 'True', 'to': "orm['wiki.Article']"}), - 'revision_number': ('django.db.models.fields.IntegerField', [], {}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '512'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), - 'user_message': ('django.db.models.fields.TextField', [], {'blank': 'True'}) - } - } - - complete_apps = ['notifications'] \ No newline at end of file diff --git a/wiki/plugins/notifications/models.py b/wiki/plugins/notifications/models.py index a632e7562..c15920879 100644 --- a/wiki/plugins/notifications/models.py +++ b/wiki/plugins/notifications/models.py @@ -27,7 +27,7 @@ class Meta: def default_url(article, urlpath=None): try: if not urlpath: - urlpath = wiki_models.URLPath.objects.get(articles=article) + urlpath = wiki_models.URLPath.objects.get(article=article) url = reverse('wiki:get', kwargs={'path': urlpath.path}) except wiki_models.URLPath.DoesNotExist: url = reverse('wiki:get', kwargs={'article_id': article.id}) diff --git a/wiki/plugins/notifications/templates/wiki/plugins/notifications/menubaritem.html b/wiki/plugins/notifications/templates/wiki/plugins/notifications/menubaritem.html index d29d4f66e..ec70cde16 100644 --- a/wiki/plugins/notifications/templates/wiki/plugins/notifications/menubaritem.html +++ b/wiki/plugins/notifications/templates/wiki/plugins/notifications/menubaritem.html @@ -1,8 +1,8 @@ {% load i18n sekizai_tags %} diff --git a/wiki/urls.py b/wiki/urls.py index 9b434de63..11a770025 100644 --- a/wiki/urls.py +++ b/wiki/urls.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from django.conf.urls.defaults import patterns, url, include +from django.conf.urls import patterns, url, include from wiki.views import article, accounts from wiki.conf import settings diff --git a/wiki/views/article.py b/wiki/views/article.py index 61258bd9b..5ee168b08 100644 --- a/wiki/views/article.py +++ b/wiki/views/article.py @@ -56,7 +56,6 @@ def get_form(self, form_class): form.fields['slug'].widget = forms.TextInputPrepend(prepend='/'+self.urlpath.path) return form - @transaction.commit_manually def form_valid(self, form): user=None ip_address = None @@ -67,36 +66,33 @@ def form_valid(self, form): elif settings.LOG_IPS_ANONYMOUS: ip_address = self.request.META.get('REMOTE_ADDR', None) try: - self.newpath = models.URLPath.create_article( - self.urlpath, - form.cleaned_data['slug'], - title=form.cleaned_data['title'], - content=form.cleaned_data['content'], - user_message=form.cleaned_data['summary'], - user=user, - ip_address=ip_address, - article_kwargs={'owner': user, - 'group': self.article.group, - 'group_read': self.article.group_read, - 'group_write': self.article.group_write, - 'other_read': self.article.other_read, - 'other_write': self.article.other_write, - }) - messages.success(self.request, _(u"New article '%s' created.") % self.newpath.article.current_revision.title) - - transaction.commit() + with transaction.atomic(): + self.newpath = models.URLPath.create_article( + self.urlpath, + form.cleaned_data['slug'], + title=form.cleaned_data['title'], + content=form.cleaned_data['content'], + user_message=form.cleaned_data['summary'], + user=user, + ip_address=ip_address, + article_kwargs={'owner': user, + 'group': self.article.group, + 'group_read': self.article.group_read, + 'group_write': self.article.group_write, + 'other_read': self.article.other_read, + 'other_write': self.article.other_write, + }) + messages.success(self.request, _(u"New article '%s' created.") % self.newpath.article.current_revision.title) + # TODO: Handle individual exceptions better and give good feedback. except Exception, e: - transaction.rollback() if self.request.user.is_superuser: messages.error(self.request, _(u"There was an error creating this article: %s") % str(e)) else: messages.error(self.request, _(u"There was an error creating this article.")) - transaction.commit() return redirect('wiki:get', '') url = self.get_success_url() - transaction.commit() return url def get_success_url(self): From 59424bd32556632724412da18220b39d33ec93be Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Wed, 23 Sep 2015 16:37:02 -0400 Subject: [PATCH 07/80] Remove south from the requirements --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index a3fd67b60..64cf3f074 100644 --- a/setup.py +++ b/setup.py @@ -42,7 +42,6 @@ def build_media_pattern(base_folder, file_extension): 'Django>=1.4', 'markdown', 'django-sekizai', - 'south', 'django-mptt', ], classifiers=[ From 100d96b1dfcdacb9400aa48b9a3673c4fcb75ee6 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Wed, 23 Sep 2015 16:37:18 -0400 Subject: [PATCH 08/80] Fix a message with incorrect format specifiers --- wiki/views/article.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wiki/views/article.py b/wiki/views/article.py index 5ee168b08..9cf41ac29 100644 --- a/wiki/views/article.py +++ b/wiki/views/article.py @@ -522,7 +522,10 @@ def change_revision(request, article, revision_id=None, urlpath=None): revision = get_object_or_404(models.ArticleRevision, article=article, id=revision_id) article.current_revision = revision article.save() - messages.success(request, _(u'The article %s is now set to display revision #%d') % (revision.title, revision.revision_number)) + messages.success(request, _(u'The article %(title)s is now set to display revision #%(revision_number)d') % { + 'title': revision.title, + 'revision_number': revision.revision_number, + }) if urlpath: return redirect("wiki:history", path=urlpath.path) else: From dc121c3b400ca02ced2158d5ac1ad135933a396f Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sun, 27 Sep 2015 16:51:29 -0400 Subject: [PATCH 09/80] Update the version number --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 64cf3f074..9a8862d47 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ def build_media_pattern(base_folder, file_extension): setup( name = "django-wiki", - version = "0.0.1", + version = "0.0.2", author = "Benjamin Bach", author_email = "benjamin@overtag.dk", description = ("A wiki system written for the Django framework."), From 3341a9879fd399079dfbf1250a3d5cfb57c3a1f2 Mon Sep 17 00:00:00 2001 From: muhammad-ammar Date: Wed, 21 Oct 2015 17:57:34 +0500 Subject: [PATCH 10/80] fix RemovedInDjango19Warning warnings TNL-3450 --- django_notify/models.py | 4 ++++ setup.py | 2 +- wiki/admin.py | 2 +- wiki/core/plugins/loader.py | 2 +- wiki/core/plugins/registry.py | 2 +- wiki/editors/markitup.py | 2 +- wiki/forms.py | 2 +- wiki/management/commands/wikiviz.py | 2 +- wiki/models/article.py | 4 ++-- wiki/models/urlpath.py | 4 ++-- 10 files changed, 15 insertions(+), 11 deletions(-) diff --git a/django_notify/models.py b/django_notify/models.py index 0a2047671..ef98c54eb 100644 --- a/django_notify/models.py +++ b/django_notify/models.py @@ -22,6 +22,7 @@ def __unicode__(self): return self.key class Meta: + app_label = 'django_notify' db_table = settings.DB_TABLE_PREFIX + '_notificationtype' verbose_name = _(u'type') verbose_name_plural = _(u'types') @@ -36,6 +37,7 @@ def __unicode__(self): return _(u"Settings for %s") % self.user.username class Meta: + app_label = 'django_notify' db_table = settings.DB_TABLE_PREFIX + '_settings' verbose_name = _(u'settings') verbose_name_plural = _(u'settings') @@ -53,6 +55,7 @@ def __unicode__(self): return _("Subscription for: %s") % str(self.settings.user.username) class Meta: + app_label = 'django_notify' db_table = settings.DB_TABLE_PREFIX + '_subscription' verbose_name = _(u'subscription') verbose_name_plural = _(u'subscriptions') @@ -98,6 +101,7 @@ def __unicode__(self): return "%s: %s" % (str(self.subscription.settings.user), self.message) class Meta: + app_label = 'django_notify' db_table = settings.DB_TABLE_PREFIX + '_notification' verbose_name = _(u'notification') verbose_name_plural = _(u'notifications') diff --git a/setup.py b/setup.py index 9a8862d47..4769768c0 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ def build_media_pattern(base_folder, file_extension): setup( name = "django-wiki", - version = "0.0.2", + version = "0.0.3", author = "Benjamin Bach", author_email = "benjamin@overtag.dk", description = ("A wiki system written for the Django framework."), diff --git a/wiki/admin.py b/wiki/admin.py index f4ceac832..693f014c9 100644 --- a/wiki/admin.py +++ b/wiki/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -from django.contrib.contenttypes.generic import GenericTabularInline +from django.contrib.contenttypes.admin import GenericTabularInline from django.utils.translation import ugettext_lazy as _ from mptt.admin import MPTTModelAdmin diff --git a/wiki/core/plugins/loader.py b/wiki/core/plugins/loader.py index da5a2baaa..71e647bb4 100644 --- a/wiki/core/plugins/loader.py +++ b/wiki/core/plugins/loader.py @@ -6,7 +6,7 @@ Thanks for the technique! """ from django.conf import settings -from django.utils.importlib import import_module +from importlib import import_module def get_module(app, modname, verbose, failfast): """ diff --git a/wiki/core/plugins/registry.py b/wiki/core/plugins/registry.py index ccd16c651..41b7f190f 100644 --- a/wiki/core/plugins/registry.py +++ b/wiki/core/plugins/registry.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from django.utils.importlib import import_module +from importlib import import_module _cache = {} _settings_forms = [] diff --git a/wiki/editors/markitup.py b/wiki/editors/markitup.py index b07be8623..3253755b5 100644 --- a/wiki/editors/markitup.py +++ b/wiki/editors/markitup.py @@ -1,5 +1,5 @@ from django import forms -from django.forms.util import flatatt +from django.forms.utils import flatatt from django.utils.encoding import force_unicode from django.utils.html import conditional_escape from django.utils.safestring import mark_safe diff --git a/wiki/forms.py b/wiki/forms.py index c0e82a3c5..826403afe 100644 --- a/wiki/forms.py +++ b/wiki/forms.py @@ -2,7 +2,7 @@ from django import forms from django.utils.translation import ugettext_lazy as _ from django.utils.safestring import mark_safe -from django.forms.util import flatatt +from django.forms.utils import flatatt from django.utils.encoding import force_unicode from django.utils.html import escape, conditional_escape, strip_tags diff --git a/wiki/management/commands/wikiviz.py b/wiki/management/commands/wikiviz.py index 8f9c51897..d94566009 100644 --- a/wiki/management/commands/wikiviz.py +++ b/wiki/management/commands/wikiviz.py @@ -74,7 +74,7 @@ try: from django.db.models.fields.generic import GenericRelation except ImportError: - from django.contrib.contenttypes.generic import GenericRelation + from django.contrib.contenttypes.fields import GenericRelation def parse_file_or_list(arg): if not arg: diff --git a/wiki/models/article.py b/wiki/models/article.py index 064dad4fb..2157e2af4 100644 --- a/wiki/models/article.py +++ b/wiki/models/article.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from django.contrib.contenttypes.models import ContentType -from django.contrib.contenttypes import generic +from django.contrib.contenttypes import fields from django.contrib.auth.models import User, Group from django.db import models from django.utils.safestring import mark_safe @@ -197,7 +197,7 @@ class ArticleForObject(models.Model): verbose_name=_('content type'), related_name="content_type_set_for_%(class)s") object_id = models.PositiveIntegerField(_('object ID')) - content_object = generic.GenericForeignKey(ct_field="content_type", fk_field="object_id") + content_object = fields.GenericForeignKey(ct_field="content_type", fk_field="object_id") is_mptt = models.BooleanField(default=False, editable=False) diff --git a/wiki/models/urlpath.py b/wiki/models/urlpath.py index f0a51a587..cbbc3f527 100644 --- a/wiki/models/urlpath.py +++ b/wiki/models/urlpath.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import logging -from django.contrib.contenttypes import generic +from django.contrib.contenttypes import fields from django.contrib.contenttypes.models import ContentType from django.contrib.sites.models import Site from django.core.exceptions import ValidationError @@ -33,7 +33,7 @@ class URLPath(MPTTModel): objects = managers.URLPathManager() _default_manager = objects - articles = generic.GenericRelation(ArticleForObject) + articles = fields.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, From 0336f5ead6cfa7d661514d6d7754c284c84ae109 Mon Sep 17 00:00:00 2001 From: muhammad-ammar Date: Thu, 5 Nov 2015 15:20:20 +0500 Subject: [PATCH 11/80] bump library version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4769768c0..0b5ff9e45 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ def build_media_pattern(base_folder, file_extension): setup( name = "django-wiki", - version = "0.0.3", + version = "0.0.4", author = "Benjamin Bach", author_email = "benjamin@overtag.dk", description = ("A wiki system written for the Django framework."), From 3cd307fb7df1804f633554e9e70060917b56a8f8 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Wed, 18 Nov 2015 07:20:28 -0500 Subject: [PATCH 12/80] Add a migration for the removed ArticleSubscription model --- .../0002_remove_article_subscription.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 wiki/migrations/0002_remove_article_subscription.py diff --git a/wiki/migrations/0002_remove_article_subscription.py b/wiki/migrations/0002_remove_article_subscription.py new file mode 100644 index 000000000..4850d48aa --- /dev/null +++ b/wiki/migrations/0002_remove_article_subscription.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_notify', '0001_initial'), + ('wiki', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='articlesubscription', + name='articleplugin_ptr', + ), + migrations.RemoveField( + model_name='articlesubscription', + name='subscription_ptr', + ), + migrations.DeleteModel( + name='ArticleSubscription', + ), + ] From 5797ed6bc80478a14c47979313dfa6b32c840261 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 20 Nov 2015 17:09:36 -0500 Subject: [PATCH 13/80] Fix a migration that could never have run successfully You can't drop the last column in a table. I don't understand what Django was thinking in generating this migration. I hope we don't need the drop-columns for backward migrations. --- setup.py | 2 +- wiki/migrations/0002_remove_article_subscription.py | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/setup.py b/setup.py index 0b5ff9e45..19dddcef4 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ def build_media_pattern(base_folder, file_extension): setup( name = "django-wiki", - version = "0.0.4", + version = "0.0.5", author = "Benjamin Bach", author_email = "benjamin@overtag.dk", description = ("A wiki system written for the Django framework."), diff --git a/wiki/migrations/0002_remove_article_subscription.py b/wiki/migrations/0002_remove_article_subscription.py index 4850d48aa..e66fd029b 100644 --- a/wiki/migrations/0002_remove_article_subscription.py +++ b/wiki/migrations/0002_remove_article_subscription.py @@ -12,14 +12,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RemoveField( - model_name='articlesubscription', - name='articleplugin_ptr', - ), - migrations.RemoveField( - model_name='articlesubscription', - name='subscription_ptr', - ), migrations.DeleteModel( name='ArticleSubscription', ), From 36e87060fd2431c3e60732fddbb4739fd1e04159 Mon Sep 17 00:00:00 2001 From: Saleem Latif Date: Tue, 23 Feb 2016 16:26:13 +0500 Subject: [PATCH 14/80] Update django wiki to support multi-tenancy --- setup.py | 2 +- testproject/testproject/settings.py | 1 + wiki/__init__.py | 1 + wiki/middleware.py | 62 +++++++++++++++++++++++++++++ wiki/models/urlpath.py | 12 +++--- 5 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 wiki/middleware.py diff --git a/setup.py b/setup.py index 19dddcef4..a799107c6 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ def build_media_pattern(base_folder, file_extension): setup( name = "django-wiki", - version = "0.0.5", + version = "0.0.6", author = "Benjamin Bach", author_email = "benjamin@overtag.dk", description = ("A wiki system written for the Django framework."), diff --git a/testproject/testproject/settings.py b/testproject/testproject/settings.py index 0ec4f1f9c..0cf198dbf 100644 --- a/testproject/testproject/settings.py +++ b/testproject/testproject/settings.py @@ -69,6 +69,7 @@ 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', + 'wiki.middleware.RequestCache', # Uncomment the next line for simple clickjacking protection: # 'django.middleware.clickjacking.XFrameOptionsMiddleware', ) diff --git a/wiki/__init__.py b/wiki/__init__.py index e69de29bb..0077f6a10 100644 --- a/wiki/__init__.py +++ b/wiki/__init__.py @@ -0,0 +1 @@ +from middleware import get_current_request diff --git a/wiki/middleware.py b/wiki/middleware.py new file mode 100644 index 000000000..cc61539db --- /dev/null +++ b/wiki/middleware.py @@ -0,0 +1,62 @@ +import threading +import importlib + +from django.conf import settings + +if getattr(settings, "WIKI_REQUEST_CACHE_MIDDLEWARE_CLASS", None): + class_name = settings.WIKI_REQUEST_CACHE_MIDDLEWARE_CLASS.split(".")[-1] + module = ".".join(settings.WIKI_REQUEST_CACHE_MIDDLEWARE_CLASS.split('.')[:-1]) + RequestCache = getattr(importlib.import_module(module), class_name) +else: + class _RequestCache(threading.local): + """ + A thread-local for storing the per-request cache. + """ + def __init__(self): + super(_RequestCache, self).__init__() + self.data = {} + self.request = None + + + REQUEST_CACHE = _RequestCache() + + + class RequestCache(object): + @classmethod + def get_request_cache(cls, name=None): + """ + This method is deprecated. Please use :func:`request_cache.get_cache`. + """ + if name is None: + return REQUEST_CACHE + else: + return REQUEST_CACHE.data.setdefault(name, {}) + + @classmethod + def get_current_request(cls): + """ + This method is deprecated. Please use :func:`request_cache.get_request`. + """ + return REQUEST_CACHE.request + + @classmethod + def clear_request_cache(cls): + """ + Empty the request cache. + """ + REQUEST_CACHE.data = {} + REQUEST_CACHE.request = None + + def process_request(self, request): + self.clear_request_cache() + REQUEST_CACHE.request = request + return None + + def process_response(self, request, response): + self.clear_request_cache() + return response + + +def get_current_request(): + """Return the request associated with the current thread.""" + return RequestCache.get_current_request() diff --git a/wiki/models/urlpath.py b/wiki/models/urlpath.py index cbbc3f527..2b5d06ca6 100644 --- a/wiki/models/urlpath.py +++ b/wiki/models/urlpath.py @@ -4,6 +4,7 @@ from django.contrib.contenttypes import fields from django.contrib.contenttypes.models import ContentType from django.contrib.sites.models import Site +from django.contrib.sites.shortcuts import get_current_site from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse from django.db import models, transaction @@ -14,6 +15,7 @@ from mptt.models import MPTTModel from wiki import managers +from wiki import get_current_request from wiki.conf import settings from wiki.core.exceptions import NoRootURL, MultipleRootURLs from wiki.models.article import ArticleRevision, ArticleForObject, Article @@ -110,7 +112,7 @@ def delete_subtree(self): @classmethod def root(cls): - site = Site.objects.get_current() + site = get_current_site(get_current_request()) root_nodes = list( cls.objects.root_nodes().filter(site=site).select_related_common() ) @@ -192,7 +194,7 @@ def get_absolute_url(self): @classmethod def create_root(cls, site=None, title="Root", **kwargs): - if not site: site = Site.objects.get_current() + if not site: site = get_current_site(get_current_request()) root_nodes = cls.objects.root_nodes().filter(site=site) if not root_nodes: # (get_or_create does not work for MPTT models??) @@ -210,7 +212,7 @@ def create_root(cls, site=None, title="Root", **kwargs): def create_article(cls, parent, slug, site=None, title="Root", article_kwargs={}, **kwargs): """Utility function: 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 = get_current_site(get_current_request()) article = Article(**article_kwargs) article.add_revision(ArticleRevision(title=title, **kwargs), save=True) @@ -241,8 +243,8 @@ def on_article_relation_save(instance, *args, **kwargs): def on_article_delete(instance, *args, **kwargs): # If an article is deleted, then throw out its URLPaths # But move all descendants to a lost-and-found node. - site = Site.objects.get_current() - + site = get_current_site(get_current_request()) + # Get the Lost-and-found path or create a new one try: lost_and_found = URLPath.objects.get(slug=settings.LOST_AND_FOUND_SLUG, From 77caec3d65806035607d8db8c24ef6cd5fa2d24d Mon Sep 17 00:00:00 2001 From: Saleem Latif Date: Mon, 7 Mar 2016 13:07:33 +0500 Subject: [PATCH 15/80] set WIKI_REQUEST_CACHE_MIDDLEWARE_CLASS to None if settings is not configured --- wiki/middleware.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/wiki/middleware.py b/wiki/middleware.py index cc61539db..05076f93f 100644 --- a/wiki/middleware.py +++ b/wiki/middleware.py @@ -3,9 +3,16 @@ from django.conf import settings -if getattr(settings, "WIKI_REQUEST_CACHE_MIDDLEWARE_CLASS", None): - class_name = settings.WIKI_REQUEST_CACHE_MIDDLEWARE_CLASS.split(".")[-1] - module = ".".join(settings.WIKI_REQUEST_CACHE_MIDDLEWARE_CLASS.split('.')[:-1]) +# Take WIKI_REQUEST_CACHE_MIDDLEWARE_CLASS from django settings, if settings is not configured or of +# WIKI_REQUEST_CACHE_MIDDLEWARE_CLASS setting is not defined then use custom middleware from django-wiki +if hasattr(settings, "WIKI_REQUEST_CACHE_MIDDLEWARE_CLASS"): + WIKI_REQUEST_CACHE_MIDDLEWARE_CLASS = settings.WIKI_REQUEST_CACHE_MIDDLEWARE_CLASS +else: + WIKI_REQUEST_CACHE_MIDDLEWARE_CLASS = None + +if WIKI_REQUEST_CACHE_MIDDLEWARE_CLASS: + class_name = WIKI_REQUEST_CACHE_MIDDLEWARE_CLASS.split(".")[-1] + module = ".".join(WIKI_REQUEST_CACHE_MIDDLEWARE_CLASS.split('.')[:-1]) RequestCache = getattr(importlib.import_module(module), class_name) else: class _RequestCache(threading.local): From cd6de29b1c72ef4158e57877adb921a0624a2e38 Mon Sep 17 00:00:00 2001 From: John Eskew Date: Wed, 6 Jul 2016 10:01:11 -0400 Subject: [PATCH 16/80] Change from deprecated IPAddressField model field to the new GenericIPAddressField model field. Bump the version number to 0.0.8. Add migration file for the field conversion. --- setup.py | 2 +- wiki/migrations/0003_ip_address_conv.py | 29 +++++++++++++++++++++++++ wiki/models/article.py | 8 ++++++- 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 wiki/migrations/0003_ip_address_conv.py diff --git a/setup.py b/setup.py index a799107c6..d87bc8e5d 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ def build_media_pattern(base_folder, file_extension): setup( name = "django-wiki", - version = "0.0.6", + version = "0.0.8", author = "Benjamin Bach", author_email = "benjamin@overtag.dk", description = ("A wiki system written for the Django framework."), diff --git a/wiki/migrations/0003_ip_address_conv.py b/wiki/migrations/0003_ip_address_conv.py new file mode 100644 index 000000000..92a107fbf --- /dev/null +++ b/wiki/migrations/0003_ip_address_conv.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wiki', '0002_remove_article_subscription'), + ] + + operations = [ + migrations.AlterField( + model_name='articlerevision', + name='ip_address', + field=models.GenericIPAddressField(verbose_name='IP address', null=True, editable=False, blank=True), + ), + migrations.AlterField( + model_name='attachmentrevision', + name='ip_address', + field=models.GenericIPAddressField(verbose_name='IP address', null=True, editable=False, blank=True), + ), + migrations.AlterField( + model_name='revisionpluginrevision', + name='ip_address', + field=models.GenericIPAddressField(verbose_name='IP address', null=True, editable=False, blank=True), + ), + ] diff --git a/wiki/models/article.py b/wiki/models/article.py index 2157e2af4..56368fa7e 100644 --- a/wiki/models/article.py +++ b/wiki/models/article.py @@ -2,6 +2,7 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes import fields from django.contrib.auth.models import User, Group +from django.db.models.fields import GenericIPAddressField from django.db import models from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ @@ -217,7 +218,12 @@ class BaseRevisionMixin(models.Model): user_message = models.TextField(blank=True,) automatic_log = models.TextField(blank=True, editable=False,) - ip_address = models.IPAddressField(_('IP address'), blank=True, null=True, editable=False) + ip_address = models.GenericIPAddressField( + _('IP address'), + blank=True, + null=True, + editable=False + ) user = models.ForeignKey(User, verbose_name=_('user'), blank=True, null=True) From ff4cd9b6e139d76ad6f120b48eabed12123bbfbf Mon Sep 17 00:00:00 2001 From: John Eskew Date: Wed, 6 Jul 2016 10:02:55 -0400 Subject: [PATCH 17/80] Get rid of unneeded whitespace. --- setup.py | 4 +- wiki/models/article.py | 102 ++++++++++++++++++++--------------------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/setup.py b/setup.py index d87bc8e5d..25f69f29c 100644 --- a/setup.py +++ b/setup.py @@ -16,8 +16,8 @@ def build_media_pattern(base_folder, file_extension): template_patterns = ( build_media_pattern("templates", "html") + build_media_pattern("static", "js") + build_media_pattern("static", "css") + - build_media_pattern("static", "png") + - build_media_pattern("static", "jpeg") + + build_media_pattern("static", "png") + + build_media_pattern("static", "jpeg") + build_media_pattern("static", "gif")) packages = find_packages() diff --git a/wiki/models/article.py b/wiki/models/article.py index 56368fa7e..f24444b0f 100644 --- a/wiki/models/article.py +++ b/wiki/models/article.py @@ -14,15 +14,15 @@ from mptt.models import MPTTModel class Article(models.Model): - + objects = managers.ArticleManager() - - current_revision = models.OneToOneField('ArticleRevision', + + current_revision = models.OneToOneField('ArticleRevision', verbose_name=_(u'current revision'), blank=True, null=True, related_name='current_set', help_text=_(u'The revision being displayed for this article. If you need to do a roll-back, simply change the value of this field.'), ) - + created = models.DateTimeField(auto_now_add=True, verbose_name=_(u'created'),) modified = models.DateTimeField(auto_now=True, verbose_name=_(u'modified'), help_text=_(u'Article properties last modified')) @@ -30,16 +30,16 @@ class Article(models.Model): owner = models.ForeignKey(User, verbose_name=_('owner'), blank=True, null=True, related_name='owned_articles', help_text=_(u'The owner of the article, usually the creator. The owner always has both read and write access.'),) - + group = models.ForeignKey(Group, verbose_name=_('group'), blank=True, null=True, help_text=_(u'Like in a UNIX file system, permissions can be given to a user according to group membership. Groups are handled through the Django auth system.'),) - + group_read = models.BooleanField(default=True, verbose_name=_(u'group read access')) group_write = models.BooleanField(default=True, verbose_name=_(u'group write access')) other_read = models.BooleanField(default=True, verbose_name=_(u'others read access')) other_write = models.BooleanField(default=True, verbose_name=_(u'others write access')) - + # TODO: Do not use kwargs, it can lead to dangerous situations with bad # permission checking patterns. Also, since there are no other keywords, # it doesn't make much sense. @@ -47,7 +47,7 @@ def can_read(self, user=None): # Deny reading access to deleted articles if user has no delete access if self.current_revision and self.current_revision.deleted and not self.can_delete(user): return False - + # Check access for other users... if user.is_anonymous() and not settings.ANONYMOUS: return False @@ -63,7 +63,7 @@ def can_read(self, user=None): if self.can_moderate(user): return True return False - + def can_write(self, user=None): # Check access for other users... if user.is_anonymous() and not settings.ANONYMOUS_WRITE: @@ -80,20 +80,20 @@ def can_write(self, user=None): if self.can_moderate(user): return True return False - + def can_delete(self, user): return permissions.can_delete(self, user) def can_moderate(self, user): return permissions.can_moderate(self, user) def can_assign(self, user): return permissions.can_assign(self, user) - + def descendant_objects(self): """NB! This generator is expensive, so use it with care!!""" for obj in self.articleforobject_set.filter(is_mptt=True): for descendant in obj.content_object.get_descendants(): yield descendant - + def get_children(self, max_num=None, user_can_read=None, **kwargs): """NB! This generator is expensive, so use it with care!!""" cnt = 0 @@ -117,7 +117,7 @@ def set_permissions_recursive(self): descendant.other_read = self.other_read descendant.other_write = self.other_write descendant.save() - + def set_group_recursive(self): for descendant in self.descendant_objects(): if descendant.INHERIT_PERMISSIONS: @@ -129,13 +129,13 @@ def set_owner_recursive(self): if descendant.INHERIT_PERMISSIONS: descendant.owner = self.owner descendant.save() - + def add_revision(self, new_revision, save=True): """ Sets the properties of a revision and ensures its the current revision. """ - assert self.id or save, ('Article.add_revision: Sorry, you cannot add a' + assert self.id or save, ('Article.add_revision: Sorry, you cannot add a' 'revision to an article that has not been saved ' 'without using save=True') if not self.id: self.save() @@ -149,7 +149,7 @@ def add_revision(self, new_revision, save=True): if save: new_revision.save() self.current_revision = new_revision if save: self.save() - + def add_object_relation(self, obj): content_type = ContentType.objects.get_for_model(obj) is_mptt = isinstance(obj, MPTTModel) @@ -158,16 +158,16 @@ def add_object_relation(self, obj): object_id=obj.id, is_mptt=is_mptt) return rel - + @classmethod def get_for_object(cls, obj): return ArticleForObject.objects.get(object_id=obj.id, content_type=ContentType.objects.get_for_model(obj)).article - + def __unicode__(self): if self.current_revision: return self.current_revision.title return _(u'Article without content (%(id)d)') % {'id': self.id} - + class Meta: app_label = settings.APP_LABEL permissions = ( @@ -175,7 +175,7 @@ class Meta: ("assign", "Can change ownership of any article"), ("grant", "Can assign permissions to other users"), ) - + def render(self, preview_content=None): if not self.current_revision: return "" @@ -186,12 +186,12 @@ def render(self, preview_content=None): extensions = plugin_registry.get_markdown_extensions() extensions += settings.MARKDOWN_EXTENSIONS return mark_safe(article_markdown(content, self, extensions=extensions)) - - + + class ArticleForObject(models.Model): - + objects = managers.ArticleFkManager() - + article = models.ForeignKey('Article', on_delete=models.CASCADE) # Same as django.contrib.comments content_type = models.ForeignKey(ContentType, @@ -199,9 +199,9 @@ class ArticleForObject(models.Model): related_name="content_type_set_for_%(class)s") object_id = models.PositiveIntegerField(_('object ID')) content_object = fields.GenericForeignKey(ct_field="content_type", fk_field="object_id") - + is_mptt = models.BooleanField(default=False, editable=False) - + class Meta: app_label = settings.APP_LABEL verbose_name = _(u'Article for object') @@ -210,14 +210,14 @@ class Meta: unique_together = ('content_type', 'object_id') 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.""" - + revision_number = models.IntegerField(editable=False, verbose_name=_(u'revision number')) user_message = models.TextField(blank=True,) automatic_log = models.TextField(blank=True, editable=False,) - + ip_address = models.GenericIPAddressField( _('IP address'), blank=True, @@ -225,13 +225,13 @@ class BaseRevisionMixin(models.Model): editable=False ) user = models.ForeignKey(User, verbose_name=_('user'), - blank=True, null=True) - + blank=True, null=True) + modified = models.DateTimeField(auto_now=True) created = models.DateTimeField(auto_now_add=True) - + previous_revision = models.ForeignKey('self', blank=True, null=True) - + # NOTE! The semantics of these fields are not related to the revision itself # but the actual related object. If the latest revision says "deleted=True" then # the related object should be regarded as deleted. @@ -245,40 +245,40 @@ def set_from_request(self, request): self.ip_address = request.META.get('REMOTE_ADDR', None) elif settings.LOG_IPS_ANONYMOUS: self.ip_address = request.META.get('REMOTE_ADDR', None) - + class Meta: abstract = True - + class ArticleRevision(BaseRevisionMixin, models.Model): """This is where main revision data is stored. To make it easier to copy, do NEVER create m2m relationships.""" - + article = models.ForeignKey('Article', on_delete=models.CASCADE, verbose_name=_(u'article')) - + # This is where the content goes, with whatever markup language is used content = models.TextField(blank=True, verbose_name=_(u'article contents')) - + # This title is automatically set from either the article's title or # the last used revision... - title = models.CharField(max_length=512, verbose_name=_(u'article title'), + title = models.CharField(max_length=512, verbose_name=_(u'article title'), null=False, blank=False, help_text=_(u'Each revision contains a title field that must be filled out, even if the title has not changed')) - + # TODO: - # Allow a revision to redirect to another *article*. This + # Allow a revision to redirect to another *article*. This # way, we can redirects and still maintain old content. #redirect = models.ForeignKey('Article', null=True, blank=True, # verbose_name=_(u'redirect'), # help_text=_(u'If set, the article will redirect to the contents of another article.'), # related_name='redirect_set') - + def __unicode__(self): return "%s (%d)" % (self.title, self.revision_number) - + def inherit_predecessor(self, article): """ Inherit certain properties from predecessor because it's very - convenient. Remember to always call this method before + convenient. Remember to always call this method before setting properties :)""" predecessor = article.current_revision self.article = predecessor.article @@ -286,14 +286,14 @@ def inherit_predecessor(self, article): self.title = predecessor.title self.deleted = predecessor.deleted self.locked = predecessor.locked - + def save(self, *args, **kwargs): if (not self.id and - not self.previous_revision and + not self.previous_revision and self.article and - self.article.current_revision and + self.article.current_revision and self.article.current_revision != self): - + self.previous_revision = self.article.current_revision if not self.revision_number: @@ -304,15 +304,15 @@ def save(self, *args, **kwargs): self.revision_number = 1 super(ArticleRevision, self).save(*args, **kwargs) - + if not self.article.current_revision: # If I'm saved from Django admin, then article.current_revision is me! self.article.current_revision = self self.article.save() - + class Meta: app_label = settings.APP_LABEL get_latest_by = 'revision_number' ordering = ('created',) unique_together = ('article', 'revision_number') - + From 4a3dc1f1e8b4c4c801443ea064f85a85888e242d Mon Sep 17 00:00:00 2001 From: benjaoming Date: Tue, 30 Oct 2012 13:27:24 +0100 Subject: [PATCH 18/80] Ensure that CreateForm fails when slug field is longer than the maximum allowed slug length (#57) --- wiki/forms.py | 3 ++- wiki/models/urlpath.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/wiki/forms.py b/wiki/forms.py index 826403afe..6782e18fd 100644 --- a/wiki/forms.py +++ b/wiki/forms.py @@ -203,7 +203,8 @@ def __init__(self, urlpath_parent, *args, **kwargs): self.urlpath_parent = urlpath_parent title = forms.CharField(label=_(u'Title'),) - slug = forms.SlugField(label=_(u'Slug'), help_text=_(u"This will be the address where your article can be found. Use only alphanumeric characters and - or _. Note that you cannot change the slug after creating the article."),) + slug = forms.SlugField(label=_(u'Slug'), help_text=_(u"This will be the address where your article can be found. Use only alphanumeric characters and - or _. Note that you cannot change the slug after creating the article."), + max_length=models.URLPath.SLUG_MAX_LENGTH) content = forms.CharField(label=_(u'Contents'), required=False, widget=getEditor().get_widget()) #@UndefinedVariable diff --git a/wiki/models/urlpath.py b/wiki/models/urlpath.py index 2b5d06ca6..e4357a9cd 100644 --- a/wiki/models/urlpath.py +++ b/wiki/models/urlpath.py @@ -41,7 +41,10 @@ class URLPath(MPTTModel): 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_MAX_LENGTH = 50 + + slug = models.SlugField(verbose_name=_(u'slug'), null=True, blank=True, + max_length=SLUG_MAX_LENGTH) site = models.ForeignKey(Site) parent = TreeForeignKey('self', null=True, blank=True, related_name='children') From 646265593cb310705f00ca241d12b94cd13425c3 Mon Sep 17 00:00:00 2001 From: Matjaz Gregoric Date: Tue, 12 Jul 2016 14:19:26 +0800 Subject: [PATCH 19/80] Increase slug size from 50 to 255. In edx-platform, default course wiki article's slug is automatically set to "{org}.{number}.{run}", which can be more than 50 characters long. The wiki breaks if the automatically assigned slug is longer than 50 characters. We usually use 255 character size for columns that store course_key in edx-platform, so I believe it makes sense to use a limit of 255 for wiki slugs, too. --- setup.py | 2 +- wiki/migrations/0004_increase_slug_size.py | 19 +++++++++++++++++++ wiki/models/urlpath.py | 4 +++- 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 wiki/migrations/0004_increase_slug_size.py diff --git a/setup.py b/setup.py index 25f69f29c..5e362a2fa 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ def build_media_pattern(base_folder, file_extension): setup( name = "django-wiki", - version = "0.0.8", + version = "0.0.9", author = "Benjamin Bach", author_email = "benjamin@overtag.dk", description = ("A wiki system written for the Django framework."), diff --git a/wiki/migrations/0004_increase_slug_size.py b/wiki/migrations/0004_increase_slug_size.py new file mode 100644 index 000000000..b9fae30be --- /dev/null +++ b/wiki/migrations/0004_increase_slug_size.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wiki', '0003_ip_address_conv'), + ] + + operations = [ + migrations.AlterField( + model_name='urlpath', + name='slug', + field=models.SlugField(max_length=255, null=True, verbose_name='slug', blank=True), + ), + ] diff --git a/wiki/models/urlpath.py b/wiki/models/urlpath.py index e4357a9cd..98adf88b6 100644 --- a/wiki/models/urlpath.py +++ b/wiki/models/urlpath.py @@ -41,7 +41,9 @@ class URLPath(MPTTModel): article = models.ForeignKey(Article, on_delete=models.CASCADE, editable=False, verbose_name=_(u'Cache lookup value for articles')) - SLUG_MAX_LENGTH = 50 + # The slug is constructed from course key and will in practice be much shorter then 255 characters + # since course keys are capped at 65 characters in the Studio (https://openedx.atlassian.net/browse/TNL-889). + SLUG_MAX_LENGTH = 255 slug = models.SlugField(verbose_name=_(u'slug'), null=True, blank=True, max_length=SLUG_MAX_LENGTH) From 906d704c871edcf1602c06a2ce6ec53df9d9121e Mon Sep 17 00:00:00 2001 From: cahrens Date: Tue, 7 Feb 2017 11:24:19 -0500 Subject: [PATCH 20/80] Fix a11y issues. TNL-6440, TNL-6439 --- setup.py | 2 +- wiki/forms.py | 3 +- wiki/templates/wiki/dir.html | 84 ++++++++++++++++++------------------ 3 files changed, 45 insertions(+), 44 deletions(-) diff --git a/setup.py b/setup.py index 5e362a2fa..9c84b6c5b 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ def build_media_pattern(base_folder, file_extension): setup( name = "django-wiki", - version = "0.0.9", + version = "0.0.10", author = "Benjamin Bach", author_email = "benjamin@overtag.dk", description = ("A wiki system written for the Django framework."), diff --git a/wiki/forms.py b/wiki/forms.py index 6782e18fd..3f6b8c1c6 100644 --- a/wiki/forms.py +++ b/wiki/forms.py @@ -362,5 +362,4 @@ class Meta: class DirFilterForm(forms.Form): - query = forms.CharField(widget=forms.TextInput(attrs={'placeholder': _(u'Filter...'), - 'class': 'search-query'})) + query = forms.CharField(label=_(u'Filter'), widget=forms.TextInput(attrs={'class': 'search-query'})) diff --git a/wiki/templates/wiki/dir.html b/wiki/templates/wiki/dir.html index 3ef7aff8f..0d8b4ebed 100644 --- a/wiki/templates/wiki/dir.html +++ b/wiki/templates/wiki/dir.html @@ -22,14 +22,14 @@ {% trans "Add article" %} -
- {{ filter_form.query }} +
+ {{ filter_form.query.label_tag }} {{ filter_form.query }} + {% if filter_query %} + + {% endif %}
- {% if filter_query %} - - {% endif %}
@@ -37,45 +37,47 @@

{% with paginator.object_list.count as cnt %} - {% blocktrans with urlpath.path as path and cnt|pluralize:_("article,articles") as articles_plur and cnt|pluralize:_("is,are") as articles_plur_verb %} - Browsing /{{ path }}. There {{ articles_plur_verb }} {{ cnt }} {{ articles_plur }} in this level. - {% endblocktrans %} + {% if filter_query %} + {% blocktrans with cnt|pluralize:_("article,articles") as articles_plur and cnt|pluralize:_("matches,match") as match_plur %} + {{ cnt }} {{ articles_plur }} in this level {{ match_plur }} your search. + {% endblocktrans %} + {% else %} + {% blocktrans with cnt|pluralize:_("article,articles") as articles_plur and cnt|pluralize:_("is,are") as articles_plur_verb %} + There {{ articles_plur_verb }} {{ cnt }} {{ articles_plur }} in this level. + {% endblocktrans %} + {% endif %} {% endwith %}

- - - - - - - {% for urlpath in directory %} - - - - - - {% empty%} +{% if directory %} +
{% trans "Title" %}{% trans "Slug" %}{% trans "Last modified" %}
- {{ urlpath.article.current_revision.title }} - › - {% if urlpath.article.current_revision.deleted %} - - {% endif %} - {% if urlpath.article.current_revision.locked %} - - {% endif %} - - {{ urlpath.slug }} - - {{ urlpath.article.current_revision.created|naturaltime }} -
- + + + - {% endfor %} -
- {% trans "There are no articles in this level" %} - {% trans "Title" %}{% trans "Slug" %}{% trans "Last modified" %}
+ {% for urlpath in directory %} + + + {{ urlpath.article.current_revision.title }} + › + {% if urlpath.article.current_revision.deleted %} + + {% endif %} + {% if urlpath.article.current_revision.locked %} + + {% endif %} + + + {{ urlpath.slug }} + + + {{ urlpath.article.current_revision.created|naturaltime }} + + + {% endfor %} + +{% endif %} {% include "wiki/includes/pagination.html" %} From 5e0c937ba78e73272e4938fa50c0ea2e23d25054 Mon Sep 17 00:00:00 2001 From: cahrens Date: Fri, 21 Jul 2017 13:26:27 -0400 Subject: [PATCH 21/80] Update test db to work with our current migations. A few of the tables had to be dropped in the process (losing their test data). --- testproject/testproject/db/prepopulated.db | Bin 1167360 -> 1167360 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/testproject/testproject/db/prepopulated.db b/testproject/testproject/db/prepopulated.db index d6949712ccf02402be9dc1b4dc06e741555c4fad..b698f291da1d95c4a9e474b69c12b8f2af6b885a 100644 GIT binary patch delta 13079 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 literal 1167360 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 fl2)5@!^8Q7K9s}e<4hDW Date: Tue, 25 Jul 2017 14:35:52 -0400 Subject: [PATCH 22/80] Remove url from futures. --- .../attachments/templates/wiki/plugins/attachments/delete.html | 1 - .../attachments/templates/wiki/plugins/attachments/history.html | 1 - .../attachments/templates/wiki/plugins/attachments/index.html | 1 - .../attachments/templates/wiki/plugins/attachments/replace.html | 1 - .../attachments/templates/wiki/plugins/attachments/search.html | 1 - wiki/plugins/images/templates/wiki/plugins/images/index.html | 1 - wiki/plugins/images/templates/wiki/plugins/images/purge.html | 1 - .../images/templates/wiki/plugins/images/revision_add.html | 1 - wiki/plugins/images/templates/wiki/plugins/images/sidebar.html | 1 - wiki/plugins/links/templates/wiki/plugins/links/sidebar.html | 1 - wiki/templates/wiki/accounts/login.html | 1 - wiki/templates/wiki/article.html | 1 - wiki/templates/wiki/base.html | 2 +- wiki/templates/wiki/create.html | 1 - wiki/templates/wiki/delete.html | 1 - wiki/templates/wiki/deleted.html | 1 - wiki/templates/wiki/dir.html | 1 - wiki/templates/wiki/edit.html | 1 - wiki/templates/wiki/error.html | 1 - wiki/templates/wiki/history.html | 1 - wiki/templates/wiki/includes/anonymous_blocked.html | 1 - wiki/templates/wiki/includes/article_menu.html | 2 +- wiki/templates/wiki/includes/breadcrumbs.html | 2 +- wiki/templates/wiki/permission_denied.html | 1 - wiki/templates/wiki/settings.html | 1 - wiki/templates/wiki/source.html | 1 - 26 files changed, 3 insertions(+), 26 deletions(-) diff --git a/wiki/plugins/attachments/templates/wiki/plugins/attachments/delete.html b/wiki/plugins/attachments/templates/wiki/plugins/attachments/delete.html index 4189f93e1..0f143bba2 100644 --- a/wiki/plugins/attachments/templates/wiki/plugins/attachments/delete.html +++ b/wiki/plugins/attachments/templates/wiki/plugins/attachments/delete.html @@ -1,6 +1,5 @@ {% extends "wiki/article.html" %} {% load wiki_tags i18n humanize %} -{% load url from future %} {% block pagetitle %}{% trans "Delete" %} "{{ attachment.current_revision.get_filename }}"{% endblock %} diff --git a/wiki/plugins/attachments/templates/wiki/plugins/attachments/history.html b/wiki/plugins/attachments/templates/wiki/plugins/attachments/history.html index b79fa594c..620cd6c7b 100644 --- a/wiki/plugins/attachments/templates/wiki/plugins/attachments/history.html +++ b/wiki/plugins/attachments/templates/wiki/plugins/attachments/history.html @@ -1,6 +1,5 @@ {% extends "wiki/article.html" %} {% load wiki_tags i18n humanize %} -{% load url from future %} {% block pagetitle %}{% trans "History of" %} "{{ attachment.current_revision.get_filename }}"{% endblock %} diff --git a/wiki/plugins/attachments/templates/wiki/plugins/attachments/index.html b/wiki/plugins/attachments/templates/wiki/plugins/attachments/index.html index 5754873f4..5b3fa18ee 100644 --- a/wiki/plugins/attachments/templates/wiki/plugins/attachments/index.html +++ b/wiki/plugins/attachments/templates/wiki/plugins/attachments/index.html @@ -1,6 +1,5 @@ {% extends "wiki/article.html" %} {% load wiki_tags i18n humanize %} -{% load url from future %} {% block pagetitle %}{% trans "Attachments" %}: {{ article.current_revision.title }}{% endblock %} diff --git a/wiki/plugins/attachments/templates/wiki/plugins/attachments/replace.html b/wiki/plugins/attachments/templates/wiki/plugins/attachments/replace.html index 39674a119..e8a7f3b8b 100644 --- a/wiki/plugins/attachments/templates/wiki/plugins/attachments/replace.html +++ b/wiki/plugins/attachments/templates/wiki/plugins/attachments/replace.html @@ -1,6 +1,5 @@ {% extends "wiki/article.html" %} {% load wiki_tags i18n humanize %} -{% load url from future %} {% block pagetitle %}{% trans "Replace" %} "{{ attachment.current_revision.get_filename }}"{% endblock %} diff --git a/wiki/plugins/attachments/templates/wiki/plugins/attachments/search.html b/wiki/plugins/attachments/templates/wiki/plugins/attachments/search.html index 55d45575d..d7d657d24 100644 --- a/wiki/plugins/attachments/templates/wiki/plugins/attachments/search.html +++ b/wiki/plugins/attachments/templates/wiki/plugins/attachments/search.html @@ -1,6 +1,5 @@ {% extends "wiki/article.html" %} {% load wiki_tags i18n humanize %} -{% load url from future %} {% block pagetitle %}{% trans "Add file to" %} "{{ article.current_revision.title }}"{% endblock %} diff --git a/wiki/plugins/images/templates/wiki/plugins/images/index.html b/wiki/plugins/images/templates/wiki/plugins/images/index.html index da5ec20ff..2861c2c71 100644 --- a/wiki/plugins/images/templates/wiki/plugins/images/index.html +++ b/wiki/plugins/images/templates/wiki/plugins/images/index.html @@ -1,6 +1,5 @@ {% extends "wiki/article.html" %} {% load wiki_tags i18n humanize thumbnail %} -{% load url from future %} {% block pagetitle %}{% trans "Images" %}: {{ article.current_revision.title }}{% endblock %} diff --git a/wiki/plugins/images/templates/wiki/plugins/images/purge.html b/wiki/plugins/images/templates/wiki/plugins/images/purge.html index 929f1d9be..cfbd3bfa4 100644 --- a/wiki/plugins/images/templates/wiki/plugins/images/purge.html +++ b/wiki/plugins/images/templates/wiki/plugins/images/purge.html @@ -1,6 +1,5 @@ {% extends "wiki/article.html" %} {% load wiki_tags i18n humanize thumbnail %} -{% load url from future %} {% block pagetitle %}{% trans "Purge deletion" %}: {{ image }}{% endblock %} diff --git a/wiki/plugins/images/templates/wiki/plugins/images/revision_add.html b/wiki/plugins/images/templates/wiki/plugins/images/revision_add.html index b4cac305a..39e5b1acc 100644 --- a/wiki/plugins/images/templates/wiki/plugins/images/revision_add.html +++ b/wiki/plugins/images/templates/wiki/plugins/images/revision_add.html @@ -1,6 +1,5 @@ {% extends "wiki/article.html" %} {% load wiki_tags i18n humanize thumbnail %} -{% load url from future %} {% block pagetitle %}{% trans "Replace image" %}: {{ image }}{% endblock %} diff --git a/wiki/plugins/images/templates/wiki/plugins/images/sidebar.html b/wiki/plugins/images/templates/wiki/plugins/images/sidebar.html index b1b3baa6e..b9c91f6bd 100644 --- a/wiki/plugins/images/templates/wiki/plugins/images/sidebar.html +++ b/wiki/plugins/images/templates/wiki/plugins/images/sidebar.html @@ -1,5 +1,4 @@ {% load i18n wiki_tags wiki_images_tags humanize thumbnail %} -{% load url from future %} @@ -23,14 +23,17 @@

{% trans "Congratulations!" %}

{% trans "You have django-wiki installed... but there are no articles. So it's time to create the first one, the root article. In the beginning, it will only be editable by administrators, but you can define permissions after." %}

- + - +
- {% wiki_form create_form %} -
+ {% wiki_form form %} +
+
+
-
+
+
{% endblock %} diff --git a/wiki/urls.py b/wiki/urls.py index 1e68ac0c2..f0c4183b6 100644 --- a/wiki/urls.py +++ b/wiki/urls.py @@ -9,7 +9,7 @@ urlpatterns = [ url(r'^$', article.ArticleView.as_view(), name='root', kwargs={'path': ''}), - url(r'^create-root/$', article.root_create, name='root_create'), + url(r'^create-root/$', article.CreateRootView.as_view(), name='root_create'), url(r'^_revision/diff/(?P\d+)/$', article.diff, name='diff'), ] @@ -69,6 +69,8 @@ url(r'^(?P.+/|)$', article.ArticleView.as_view(), name='get'), ] +app_name = 'wiki' + def get_pattern(app_name="wiki"): """Every url resolution takes place as "wiki:view_name". You should not attempt to have multiple deployments of the wiki in a diff --git a/wiki/views/article.py b/wiki/views/article.py index d7115935c..f9b3ba929 100644 --- a/wiki/views/article.py +++ b/wiki/views/article.py @@ -7,8 +7,7 @@ from django.contrib.auth.decorators import login_required from django.db import transaction from django.db.models import Q -from django.shortcuts import get_object_or_404, redirect, render_to_response -from django.template.context import RequestContext +from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils.decorators import method_decorator from django.utils.translation import ugettext as _ @@ -34,14 +33,14 @@ class ArticleView(ArticleMixin, TemplateView): @method_decorator(get_article(can_read=True)) def dispatch(self, request, article, *args, **kwargs): return super(ArticleView, self).dispatch(request, article, *args, **kwargs) - + def get_context_data(self, **kwargs): kwargs['selected_tab'] = 'view' return ArticleMixin.get_context_data(self, **kwargs) class Create(FormView, ArticleMixin): - + form_class = forms.CreateForm template_name="wiki/create.html" @@ -98,13 +97,13 @@ def form_valid(self, form): else: messages.error(self.request, _(u"There was an error creating this article.")) return redirect('wiki:get', '') - + url = self.get_success_url() return url - + def get_success_url(self): return redirect('wiki:get', self.newpath.path) - + def get_context_data(self, **kwargs): c = ArticleMixin.get_context_data(self, **kwargs) # Needed since Django 1.9 because get_context_data is no longer called @@ -119,14 +118,14 @@ def get_context_data(self, **kwargs): class Delete(FormView, ArticleMixin): - + form_class = forms.DeleteForm template_name="wiki/delete.html" - + @method_decorator(get_article(can_write=True, not_locked=True, can_delete=True)) def dispatch(self, request, article, *args, **kwargs): return self.dispatch1(request, article, *args, **kwargs) - + def dispatch1(self, request, article, *args, **kwargs): """Deleted view needs to access this method without a decorator, therefore it is separate.""" @@ -149,7 +148,7 @@ def dispatch1(self, request, article, *args, **kwargs): self.cannot_delete_root = True return super(Delete, self).dispatch(request, article, *args, **kwargs) - + def get_initial(self): return {'revision': self.article.current_revision} @@ -158,18 +157,18 @@ def get_form(self, form_class=None): if self.article.can_moderate(self.request.user): form.fields['purge'].widget = forms.forms.CheckboxInput() return form - + def get_form_kwargs(self): kwargs = FormView.get_form_kwargs(self) kwargs['article'] = self.article kwargs['has_children'] = bool(self.children_slice) return kwargs - + def form_valid(self, form): cd = form.cleaned_data - + purge = cd['purge'] - + #If we are purging, only moderators can delete articles with children cannot_delete_children = False can_moderate = self.article.can_moderate(self.request.user) @@ -179,7 +178,7 @@ def form_valid(self, form): if self.cannot_delete_root or cannot_delete_children: messages.error(self.request, _(u'This article cannot be deleted because it has children or is a root article.')) return redirect('wiki:get', article_id=self.article.id) - + if can_moderate and purge: # First, remove children @@ -197,10 +196,10 @@ def form_valid(self, form): self.article.add_revision(revision) messages.success(self.request, _(u'The article "%s" is now marked as deleted! Thanks for keeping the site free from unwanted material!') % revision.title) return self.get_success_url() - + def get_success_url(self): return redirect(self.next) - + def get_context_data(self, **kwargs): cannot_delete_children = False if self.children_slice and not self.article.can_moderate(self.request.user): @@ -220,10 +219,10 @@ def get_context_data(self, **kwargs): class Edit(ArticleMixin, FormView): """Edit an article and process sidebar plugins.""" - + form_class = forms.EditForm template_name="wiki/edit.html" - + @method_decorator(get_article(can_write=True, not_locked=True)) def dispatch(self, request, article, *args, **kwargs): self.sidebar_plugins = plugin_registry.get_sidebar() @@ -252,7 +251,7 @@ def get_sidebar_form_classes(self): for cnt, plugin in enumerate(self.sidebar_plugins): form_classes['form%d' % cnt] = (plugin, plugin.sidebar.get('form_class', None)) return form_classes - + def get(self, request, *args, **kwargs): # Generate sidebar forms self.sidebar_forms = [] @@ -264,7 +263,7 @@ def get(self, request, *args, **kwargs): form = None self.sidebar.append((plugin, form)) return super(Edit, self).get(request, *args, **kwargs) - + def post(self, request, *args, **kwargs): # Generate sidebar forms self.sidebar_forms = [] @@ -289,9 +288,9 @@ def post(self, request, *args, **kwargs): form = None self.sidebar.append((plugin, form)) return super(Edit, self).post(request, *args, **kwargs) - + def form_valid(self, form): - """Create a new article revision when the edit form is valid + """Create a new article revision when the edit form is valid (does not concern any sidebar forms!).""" revision = models.ArticleRevision() revision.inherit_predecessor(self.article) @@ -303,13 +302,13 @@ def form_valid(self, form): self.article.add_revision(revision) messages.success(self.request, _(u'A new revision of the article was successfully added.')) return self.get_success_url() - + def get_success_url(self): """Go to the article view page when the article has been saved""" if self.urlpath: return redirect("wiki:get", path=self.urlpath.path) return redirect('wiki:get', article_id=self.article.id) - + def get_context_data(self, **kwargs): # Needed for Django 1.9 because get_context_data is no longer called # with the form instance @@ -325,16 +324,16 @@ def get_context_data(self, **kwargs): class Deleted(Delete): """Tell a user that an article has been deleted. If user has permissions, let user restore and possibly purge the deleted article and children.""" - + template_name="wiki/deleted.html" form_class = forms.DeleteForm - + @method_decorator(get_article(can_read=True, deleted_contents=True)) def dispatch(self, request, article, *args, **kwargs): - + self.urlpath = kwargs.get('urlpath', None) self.article = article - + if self.urlpath: deleted_ancestor = self.urlpath.first_deleted_ancestor() if deleted_ancestor is None: @@ -343,16 +342,16 @@ def dispatch(self, request, article, *args, **kwargs): elif deleted_ancestor != self.urlpath: # An ancestor was deleted, so redirect to that deleted page return redirect('wiki:deleted', path=deleted_ancestor.path) - + else: if not article.current_revision.deleted: return redirect('wiki:get', article_id=article.id) - + # Restore if request.GET.get('restore', False): can_restore = not article.current_revision.locked and article.can_delete(request.user) can_restore = can_restore or article.can_moderate(request.user) - + if can_restore: revision = models.ArticleRevision() revision.inherit_predecessor(self.article) @@ -365,9 +364,9 @@ def dispatch(self, request, article, *args, **kwargs): return redirect('wiki:get', path=self.urlpath.path) else: return redirect('wiki:get', article_id=article.id) - + return super(Deleted, self).dispatch1(request, article, *args, **kwargs) - + def get_initial(self): return {'revision': self.article.current_revision, 'purge': True} @@ -379,30 +378,30 @@ def get_context_data(self, **kwargs): kwargs['form'] = self.get_form() kwargs['purge_form'] = kwargs.pop('form', None) return super(Delete, self).get_context_data(**kwargs) - + class Source(ArticleMixin, TemplateView): template_name="wiki/source.html" - + @method_decorator(get_article(can_read=True)) def dispatch(self, request, article, *args, **kwargs): return super(Source, self).dispatch(request, article, *args, **kwargs) - + def get_context_data(self, **kwargs): kwargs['selected_tab'] = 'source' return ArticleMixin.get_context_data(self, **kwargs) class History(ListView, ArticleMixin): - + template_name="wiki/history.html" allow_empty = True context_object_name = 'revisions' paginate_by = 10 - + def get_queryset(self): return models.ArticleRevision.objects.filter(article=self.article).order_by('-created') - + def get_context_data(self, **kwargs): # Is this a bit of a hack? Use better inheritance? kwargs_article = ArticleMixin.get_context_data(self, **kwargs) @@ -411,20 +410,20 @@ def get_context_data(self, **kwargs): kwargs.update(kwargs_listview) kwargs['selected_tab'] = 'history' return kwargs - + @method_decorator(get_article(can_read=True)) def dispatch(self, request, article, *args, **kwargs): return super(History, self).dispatch(request, article, *args, **kwargs) class Dir(ListView, ArticleMixin): - + template_name="wiki/dir.html" allow_empty = True context_object_name = 'directory' model = models.URLPath paginate_by = 30 - + @method_decorator(get_article(can_read=True)) def dispatch(self, request, article, *args, **kwargs): self.filter_form = forms.DirFilterForm(request.GET) @@ -443,7 +442,7 @@ def get_queryset(self): children = children.active() children = children.select_related_common().order_by('article__current_revision__title') return children - + def get_context_data(self, **kwargs): kwargs_article = ArticleMixin.get_context_data(self, **kwargs) kwargs_listview = ListView.get_context_data(self, **kwargs) @@ -451,7 +450,7 @@ def get_context_data(self, **kwargs): kwargs.update(kwargs_listview) kwargs['filter_query'] = self.query kwargs['filter_form'] = self.filter_form - + # Update each child's ancestor cache so the lookups don't have # to be repeated. updated_children = kwargs[self.context_object_name] @@ -463,7 +462,7 @@ def get_context_data(self, **kwargs): class Plugin(View): - + def dispatch(self, request, path=None, slug=None, **kwargs): kwargs['path'] = path for plugin in plugin_registry.get_plugins().values(): @@ -472,15 +471,15 @@ def dispatch(self, request, path=None, slug=None, **kwargs): class Settings(ArticleMixin, TemplateView): - + permission_form_class = forms.PermissionsForm template_name="wiki/settings.html" - + @method_decorator(login_required) @method_decorator(get_article(can_read=True)) def dispatch(self, request, article, *args, **kwargs): return super(Settings, self).dispatch(request, article, *args, **kwargs) - + def get_form_classes(self,): """ Return all settings forms that can be filled in @@ -494,9 +493,9 @@ def get_form_classes(self,): # could be mixed up with a different instance # Use strategy from Edit view... setattr(settings_forms[i], 'action', 'form%d' % i) - + return settings_forms - + def post(self, *args, **kwargs): self.forms = [] for Form in self.get_form_classes(): @@ -514,24 +513,24 @@ def post(self, *args, **kwargs): form = Form(self.article, self.request) self.forms.append(form) return super(Settings, self).get(*args, **kwargs) - + def get(self, *args, **kwargs): self.forms = [] - + # There is a bug where articles fetched with select_related have bad boolean field https://code.djangoproject.com/ticket/15040 # We fetch a fresh new article for this reason new_article = models.Article.objects.get(id=self.article.id) - + for Form in self.get_form_classes(): self.forms.append(Form(new_article, self.request)) - + return super(Settings, self).get(*args, **kwargs) def get_success_url(self): if self.urlpath: return redirect('wiki:settings', path=self.urlpath.path) return redirect('wiki:settings', article_id=self.article.id) - + def get_context_data(self, **kwargs): kwargs['selected_tab'] = 'settings' kwargs['forms'] = self.forms @@ -554,9 +553,9 @@ def change_revision(request, article, revision_id=None, urlpath=None): return redirect('wiki:history', article_id=article.id) class Preview(ArticleMixin, TemplateView): - + template_name="wiki/preview_inline.html" - + @method_decorator(get_article(can_read=True, deleted_contents=True)) def dispatch(self, request, article, *args, **kwargs): revision_id = request.GET.get('r', None) @@ -568,7 +567,7 @@ def dispatch(self, request, article, *args, **kwargs): else: self.revision = None return super(Preview, self).dispatch(request, article, *args, **kwargs) - + def post(self, request, *args, **kwargs): edit_form = forms.EditForm(self.article.current_revision, request.POST, preview=True) if edit_form.is_valid(): @@ -576,54 +575,54 @@ def post(self, request, *args, **kwargs): self.content = edit_form.cleaned_data['content'] self.preview = True return super(Preview, self).get(request, *args, **kwargs) - + def get(self, request, *args, **kwargs): if self.revision and not self.title: self.title = self.revision.title if self.revision and not self.content: self.content = self.revision.content return super(Preview, self).get( request, *args, **kwargs) - + def get_context_data(self, **kwargs): kwargs['title'] = self.title kwargs['revision'] = self.revision kwargs['content'] = self.content kwargs['preview'] = self.preview return ArticleMixin.get_context_data(self, **kwargs) - + @json_view def diff(request, revision_id, other_revision_id=None): - + revision = get_object_or_404(models.ArticleRevision, id=revision_id) - + if not other_revision_id: other_revision = revision.previous_revision - + baseText = other_revision.content if other_revision else "" newText = revision.content - + differ = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) diff = differ.compare(baseText.splitlines(1), newText.splitlines(1)) - + other_changes = [] - + if not other_revision or other_revision.title != revision.title: other_changes.append((_(u'New title'), revision.title)) - + return dict(diff=list(diff), other_changes=other_changes) # TODO: Throw in a class-based view @get_article(can_write=True) def merge(request, article, revision_id, urlpath=None, template_file="wiki/preview_inline.html", preview=False): - + revision = get_object_or_404(models.ArticleRevision, article=article, id=revision_id) - + current_text = article.current_revision.content if article.current_revision else "" new_text = revision.content - + content = simple_merge(current_text, new_text) - + # Save new revision if not preview: old_revision = article.current_revision @@ -646,38 +645,46 @@ def merge(request, article, revision_id, urlpath=None, template_file="wiki/previ return redirect('wiki:edit', article_id=article.id) - c = RequestContext(request, {'article': article, - 'title': article.current_revision.title, - 'revision': None, - 'merge1': revision, - 'merge2': article.current_revision, - 'merge': True, - 'content': content}) - return render_to_response(template_file, context_instance=c) - -# TODO: Should be a class-based view -def root_create(request): - try: - root = models.URLPath.root() - if not root.article: + c = {'article': article, + 'title': article.current_revision.title, + 'revision': None, + 'merge1': revision, + 'merge2': article.current_revision, + 'merge': True, + 'content': content} + return render(request, template_file, c) + + +class CreateRootView(FormView): + form_class = forms.CreateRootForm + template_name = 'wiki/article/create_root.html' + + def dispatch(self, request, *args, **kwargs): + if not request.user.is_superuser: + return redirect(settings.LOGIN_URL + "?next=" + reverse("wiki:root_create")) + + try: + root = models.URLPath.root() + except NoRootURL: + pass + else: + if root.article: + return redirect('wiki:get', path=root.path) + # TODO: This is too dangerous... let's say there is no root.article and we end up here, # then it might cascade to delete a lot of things on an existing installation.... / benjaoming root.delete() - raise NoRootURL - return redirect('wiki:get', path=root.path) - except NoRootURL: - pass - if not request.user.is_superuser: - return redirect(settings.LOGIN_URL + "?next=" + reverse("wiki:root_create")) - if request.method == 'POST': - create_form = forms.CreateRootForm(request.POST) - if create_form.is_valid(): - models.URLPath.create_root(title=create_form.cleaned_data["title"], - content=create_form.cleaned_data["content"]) - return redirect("wiki:root") - else: - create_form = forms.CreateRootForm() - - c = RequestContext(request, {'create_form': create_form, - 'editor': editors.getEditor(),}) - return render_to_response("wiki/article/create_root.html", context_instance=c) + return super(CreateRootView, self).dispatch(request, *args, **kwargs) + + def form_valid(self, form): + models.URLPath.create_root( + title=form.cleaned_data["title"], + content=form.cleaned_data["content"], + request=self.request + ) + return redirect("wiki:root") + + def get_context_data(self, **kwargs): + data = super(CreateRootView, self).get_context_data(**kwargs) + data['editor'] = editors.getEditor() + return data From 50198d7cf059f8397ef1387059fcf5defade9512 Mon Sep 17 00:00:00 2001 From: Eric Herrera Date: Mon, 15 Jun 2020 23:25:31 -0500 Subject: [PATCH 54/80] Update classifiers. Update setup.py install_requirements definition. Create pip_tools, base, test, tox and travis requirements files. Create Makefile and add upgrade command. Include requirements files generated using upgrade command. Remove old requirements file. Update openedx.yml. --- Makefile | 6 ++++++ openedx.yaml | 1 + requirements.txt | 7 ------- requirements/base.in | 11 +++++++++++ requirements/base.txt | 21 ++++++++++++++++++++ requirements/constraints.txt | 17 +++++++++++++++++ requirements/docs.in | 5 +++++ requirements/docs.txt | 34 +++++++++++++++++++++++++++++++++ requirements/pip_tools.in | 3 +++ requirements/pip_tools.txt | 12 ++++++++++++ setup.py | 37 ++++++++++++++++++++++++++++-------- 11 files changed, 139 insertions(+), 15 deletions(-) create mode 100644 Makefile delete mode 100644 requirements.txt create mode 100644 requirements/base.in create mode 100644 requirements/base.txt create mode 100644 requirements/constraints.txt create mode 100644 requirements/docs.in create mode 100644 requirements/docs.txt create mode 100644 requirements/pip_tools.in create mode 100644 requirements/pip_tools.txt diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..791490d3a --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +upgrade: export CUSTOM_COMPILE_COMMAND=make upgrade +upgrade: + pip install -q -r requirements/pip_tools.txt + pip-compile --upgrade -o requirements/pip_tools.txt requirements/pip_tools.in + pip-compile --upgrade -o requirements/base.txt requirements/base.in + pip-compile --upgrade -o requirements/docs.txt requirements/docs.in diff --git a/openedx.yaml b/openedx.yaml index 69b973180..44b38b82d 100644 --- a/openedx.yaml +++ b/openedx.yaml @@ -9,3 +9,4 @@ tags: - library oeps: oep-7: True + oep-18: True diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index b89deada3..000000000 --- a/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -django>=1.8,<2.0 -Markdown>=2.6,<2.7 -django-sekizai>=0.10 -django-mptt>=0.8.6,<0.9 -sorl-thumbnail>=12,<13 -six>=1.10.0,<2.0.0 -bleach==2.1.4 diff --git a/requirements/base.in b/requirements/base.in new file mode 100644 index 000000000..640665e9d --- /dev/null +++ b/requirements/base.in @@ -0,0 +1,11 @@ +# Core requirements for using this package +-c constraints.txt + +django +Markdown +django-sekizai +django-mptt +Pillow +sorl-thumbnail +six +bleach diff --git a/requirements/base.txt b/requirements/base.txt new file mode 100644 index 000000000..b36455985 --- /dev/null +++ b/requirements/base.txt @@ -0,0 +1,21 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# make upgrade +# +bleach==3.1.5 # via -r requirements/base.in +django-classy-tags==1.0.0 # via django-sekizai +django-js-asset==1.2.2 # via django-mptt +django-mptt==0.11.0 # via -r requirements/base.in +django-sekizai==1.1.0 # via -r requirements/base.in +django==2.2.13 # via -c requirements/constraints.txt, -r requirements/base.in, django-classy-tags, django-mptt, django-sekizai +markdown==2.6.11 # via -c requirements/constraints.txt, -r requirements/base.in +packaging==20.4 # via bleach +pillow==7.1.2 # via -r requirements/base.in +pyparsing==2.4.7 # via packaging +pytz==2020.1 # via django +six==1.15.0 # via -r requirements/base.in, bleach, django-classy-tags, django-sekizai, packaging +sorl-thumbnail==12.6.3 # via -r requirements/base.in +sqlparse==0.3.1 # via django +webencodings==0.5.1 # via bleach diff --git a/requirements/constraints.txt b/requirements/constraints.txt new file mode 100644 index 000000000..e5853f8af --- /dev/null +++ b/requirements/constraints.txt @@ -0,0 +1,17 @@ +# Version constraints for pip-installation. +# +# This file doesn't install any packages. It specifies version constraints +# that will be applied if a package is needed. +# +# When pinning something here, please provide an explanation of why. Ideally, +# link to other information that will help people in the future to remove the +# pin when possible. Writing an issue against the offending project and +# linking to it here is good. + +# TODO: Many pinned dependencies should be unpinned and/or moved to this constraints file. + +# Use latest Django LTS version +Django<2.3.0 + +# Use version 2.6 to avoid markdown errors +Markdown<2.7 diff --git a/requirements/docs.in b/requirements/docs.in new file mode 100644 index 000000000..832d305e3 --- /dev/null +++ b/requirements/docs.in @@ -0,0 +1,5 @@ +# Used by doc builds (like for edx.readthedocs.io) +-c constraints.txt + +edx-sphinx-theme +Sphinx diff --git a/requirements/docs.txt b/requirements/docs.txt new file mode 100644 index 000000000..512fb9abb --- /dev/null +++ b/requirements/docs.txt @@ -0,0 +1,34 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# make upgrade +# +alabaster==0.7.12 # via sphinx +babel==2.8.0 # via sphinx +certifi==2020.4.5.2 # via requests +chardet==3.0.4 # via requests +docutils==0.16 # via sphinx +edx-sphinx-theme==1.5.0 # via -r requirements/docs.in +idna==2.9 # via requests +imagesize==1.2.0 # via sphinx +jinja2==2.11.2 # via sphinx +markupsafe==1.1.1 # via jinja2 +packaging==20.4 # via sphinx +pygments==2.6.1 # via sphinx +pyparsing==2.4.7 # via packaging +pytz==2020.1 # via babel +requests==2.23.0 # via sphinx +six==1.15.0 # via edx-sphinx-theme, packaging +snowballstemmer==2.0.0 # via sphinx +sphinx==3.1.0 # via -r requirements/docs.in, edx-sphinx-theme +sphinxcontrib-applehelp==1.0.2 # via sphinx +sphinxcontrib-devhelp==1.0.2 # via sphinx +sphinxcontrib-htmlhelp==1.0.3 # via sphinx +sphinxcontrib-jsmath==1.0.1 # via sphinx +sphinxcontrib-qthelp==1.0.3 # via sphinx +sphinxcontrib-serializinghtml==1.1.4 # via sphinx +urllib3==1.25.9 # via requests + +# The following packages are considered to be unsafe in a requirements file: +# setuptools diff --git a/requirements/pip_tools.in b/requirements/pip_tools.in new file mode 100644 index 000000000..b10701838 --- /dev/null +++ b/requirements/pip_tools.in @@ -0,0 +1,3 @@ +# Dependencies to run compile tools +-c constraints.txt +pip-tools # Contains pip-compile, used to generate pip requirements files diff --git a/requirements/pip_tools.txt b/requirements/pip_tools.txt new file mode 100644 index 000000000..279019f74 --- /dev/null +++ b/requirements/pip_tools.txt @@ -0,0 +1,12 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# make upgrade +# +click==7.1.2 # via pip-tools +pip-tools==5.2.1 # via -r requirements/pip_tools.in +six==1.15.0 # via pip-tools + +# The following packages are considered to be unsafe in a requirements file: +# pip diff --git a/setup.py b/setup.py index f6aa01561..2db1eeec5 100644 --- a/setup.py +++ b/setup.py @@ -21,6 +21,29 @@ def build_media_pattern(base_folder, file_extension): return ["%s/%s*.%s" % (base_folder, "*/"*x, file_extension) for x in range(10)] +def load_requirements(*requirements_paths): + """ + Load all requirements from the specified requirements files. + Returns a list of requirement strings. + """ + requirements = set() + for path in requirements_paths: + with open(path) as reqs: + requirements.update( + line.split('#')[0].strip() for line in reqs + if is_requirement(line.strip()) + ) + return list(requirements) + + +def is_requirement(line): + """ + Return True if the requirement line is a package requirement; + that is, it is not blank, a comment, a URL, or an included file. + """ + return line and not line.startswith(('-r', '#', '-e', 'git+', '-c')) + + template_patterns = ( build_media_pattern("templates", "html") + build_media_pattern("static", "js") + @@ -38,22 +61,17 @@ def build_media_pattern(base_folder, file_extension): ) setup( - name = "django-wiki", + name="django-wiki", version="0.0.27", author="Benjamin Bach", author_email="benjamin@overtag.dk", description=("A wiki system written for the Django framework."), license="GPLv3", keywords="django wiki markdown", - packages=find_packages(exclude=["testproject","testproject.*"]), + packages=find_packages(exclude=["testproject", "testproject.*"]), long_description=read('README.md'), zip_safe=False, - install_requires=[ - 'Django<3.0', - 'markdown', - 'django-sekizai', - 'django-mptt', - ], + install_requires=load_requirements('requirements/base.in'), classifiers=[ 'Development Status :: 2 - Pre-Alpha', 'License :: OSI Approved :: GPLv3', @@ -65,6 +83,9 @@ def build_media_pattern(base_folder, file_extension): 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.8', + 'Framework :: Django', + 'Framework :: Django :: 2.2', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Software Development', 'Topic :: Software Development :: Libraries :: Application Frameworks', From bfbec8b1e3bfc1a3604e958c207d2f3672776fcd Mon Sep 17 00:00:00 2001 From: Eric Herrera Date: Tue, 30 Jun 2020 19:21:58 -0500 Subject: [PATCH 55/80] Address requirements comments. --- requirements/base.in | 11 +++++------ requirements/base.txt | 1 - requirements/docs.txt | 8 ++++---- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/requirements/base.in b/requirements/base.in index 640665e9d..bb1d13c64 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -1,11 +1,10 @@ # Core requirements for using this package -c constraints.txt +bleach # Use for sanitizing HTML content in wiki articles django -Markdown -django-sekizai django-mptt -Pillow -sorl-thumbnail -six -bleach +django-sekizai +Markdown +six # Use for strings and unicode compatibility +sorl-thumbnail # Required by wiki.plugins.images diff --git a/requirements/base.txt b/requirements/base.txt index b36455985..638118252 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -12,7 +12,6 @@ django-sekizai==1.1.0 # via -r requirements/base.in django==2.2.13 # via -c requirements/constraints.txt, -r requirements/base.in, django-classy-tags, django-mptt, django-sekizai markdown==2.6.11 # via -c requirements/constraints.txt, -r requirements/base.in packaging==20.4 # via bleach -pillow==7.1.2 # via -r requirements/base.in pyparsing==2.4.7 # via packaging pytz==2020.1 # via django six==1.15.0 # via -r requirements/base.in, bleach, django-classy-tags, django-sekizai, packaging diff --git a/requirements/docs.txt b/requirements/docs.txt index 512fb9abb..34bc60738 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -6,11 +6,11 @@ # alabaster==0.7.12 # via sphinx babel==2.8.0 # via sphinx -certifi==2020.4.5.2 # via requests +certifi==2020.6.20 # via requests chardet==3.0.4 # via requests docutils==0.16 # via sphinx edx-sphinx-theme==1.5.0 # via -r requirements/docs.in -idna==2.9 # via requests +idna==2.10 # via requests imagesize==1.2.0 # via sphinx jinja2==2.11.2 # via sphinx markupsafe==1.1.1 # via jinja2 @@ -18,10 +18,10 @@ packaging==20.4 # via sphinx pygments==2.6.1 # via sphinx pyparsing==2.4.7 # via packaging pytz==2020.1 # via babel -requests==2.23.0 # via sphinx +requests==2.24.0 # via sphinx six==1.15.0 # via edx-sphinx-theme, packaging snowballstemmer==2.0.0 # via sphinx -sphinx==3.1.0 # via -r requirements/docs.in, edx-sphinx-theme +sphinx==3.1.1 # via -r requirements/docs.in, edx-sphinx-theme sphinxcontrib-applehelp==1.0.2 # via sphinx sphinxcontrib-devhelp==1.0.2 # via sphinx sphinxcontrib-htmlhelp==1.0.3 # via sphinx From c2fe2d0f938fcd908eaaa932c073d4ae6e52b766 Mon Sep 17 00:00:00 2001 From: Eric Herrera Date: Sat, 6 Jun 2020 17:55:14 -0500 Subject: [PATCH 56/80] Initial changes to support tests. Fix tests clearing cache and updating some methods. Use pytest to run the tests. Bump version --- .travis.yml | 13 ++++ MANIFEST.in | 8 +++ Makefile | 7 ++ requirements/base.txt | 2 +- requirements/django.txt | 1 + requirements/test.in | 8 +++ requirements/test.txt | 31 +++++++++ requirements/tox.in | 4 ++ requirements/tox.txt | 20 ++++++ requirements/travis.in | 4 ++ requirements/travis.txt | 20 ++++++ setup.py | 6 +- testproject/pytest.ini | 3 + tox.ini | 10 +++ wiki/tests.py | 140 ++++++++++++++++++++++++++++++++++++---- 15 files changed, 260 insertions(+), 17 deletions(-) create mode 100644 .travis.yml create mode 100644 MANIFEST.in create mode 100644 requirements/django.txt create mode 100644 requirements/test.in create mode 100644 requirements/test.txt create mode 100644 requirements/tox.in create mode 100644 requirements/tox.txt create mode 100644 requirements/travis.in create mode 100644 requirements/travis.txt create mode 100644 testproject/pytest.ini create mode 100644 tox.ini diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..b8baa020e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +language: python +python: + - 3.5 + - 3.8 +install: + - pip install -r requirements/travis.txt +matrix: + - python: 3.5 + env: TOXENV=py35-django22 + - python: 3.8 + env: TOXENV=py38-django22 +script: + - tox diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..79de37839 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,8 @@ +include CHANGELOG.rst +include CONTRIBUTING.rst +include LICENSE.txt +include README.rst +recursive-include wiki *.html *.png *.gif *js *.css *jpg *jpeg *svg *py *.txt *.json +recursive-include django_notify *.html *.png *.gif *js *.css *jpg *jpeg *svg *py *.txt *.json +include requirements/base.in + diff --git a/Makefile b/Makefile index 791490d3a..73686e09c 100644 --- a/Makefile +++ b/Makefile @@ -4,3 +4,10 @@ upgrade: pip-compile --upgrade -o requirements/pip_tools.txt requirements/pip_tools.in pip-compile --upgrade -o requirements/base.txt requirements/base.in pip-compile --upgrade -o requirements/docs.txt requirements/docs.in + pip-compile --upgrade -o requirements/test.txt requirements/test.in + pip-compile --upgrade -o requirements/tox.txt requirements/tox.in + pip-compile --upgrade -o requirements/travis.txt requirements/travis.in + # Let tox control the Django version for tests + grep -e "^django==" requirements/base.txt > requirements/django.txt + sed '/^[dD]jango==/d' requirements/test.txt > requirements/test.tmp + mv requirements/test.tmp requirements/test.txt diff --git a/requirements/base.txt b/requirements/base.txt index 638118252..1380df0b9 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -9,7 +9,7 @@ django-classy-tags==1.0.0 # via django-sekizai django-js-asset==1.2.2 # via django-mptt django-mptt==0.11.0 # via -r requirements/base.in django-sekizai==1.1.0 # via -r requirements/base.in -django==2.2.13 # via -c requirements/constraints.txt, -r requirements/base.in, django-classy-tags, django-mptt, django-sekizai +django==2.2.14 # via -c requirements/constraints.txt, -r requirements/base.in, django-classy-tags, django-mptt, django-sekizai markdown==2.6.11 # via -c requirements/constraints.txt, -r requirements/base.in packaging==20.4 # via bleach pyparsing==2.4.7 # via packaging diff --git a/requirements/django.txt b/requirements/django.txt new file mode 100644 index 000000000..3cdbc8d88 --- /dev/null +++ b/requirements/django.txt @@ -0,0 +1 @@ +django==2.2.14 # via -c requirements/constraints.txt, -r requirements/base.in, django-classy-tags, django-mptt, django-sekizai diff --git a/requirements/test.in b/requirements/test.in new file mode 100644 index 000000000..8154ccb2c --- /dev/null +++ b/requirements/test.in @@ -0,0 +1,8 @@ +# Packages required for testing +-c constraints.txt + +-r base.txt + +pytest +pytest-cov +pytest-django diff --git a/requirements/test.txt b/requirements/test.txt new file mode 100644 index 000000000..eb6cdf418 --- /dev/null +++ b/requirements/test.txt @@ -0,0 +1,31 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# make upgrade +# +attrs==19.3.0 # via pytest +bleach==3.1.5 # via -r requirements/base.txt +coverage==5.1 # via pytest-cov +django-classy-tags==1.0.0 # via -r requirements/base.txt, django-sekizai +django-js-asset==1.2.2 # via -r requirements/base.txt, django-mptt +django-mptt==0.11.0 # via -r requirements/base.txt +django-sekizai==1.1.0 # via -r requirements/base.txt +importlib-metadata==1.7.0 # via pluggy, pytest +markdown==2.6.11 # via -c requirements/constraints.txt, -r requirements/base.txt +more-itertools==8.4.0 # via pytest +packaging==20.4 # via -r requirements/base.txt, bleach, pytest +pathlib2==2.3.5 # via pytest +pluggy==0.13.1 # via pytest +py==1.9.0 # via pytest +pyparsing==2.4.7 # via -r requirements/base.txt, packaging +pytest-cov==2.10.0 # via -r requirements/test.in +pytest-django==3.9.0 # via -r requirements/test.in +pytest==5.4.3 # via -r requirements/test.in, pytest-cov, pytest-django +pytz==2020.1 # via -r requirements/base.txt, django +six==1.15.0 # via -r requirements/base.txt, bleach, django-classy-tags, django-sekizai, packaging, pathlib2 +sorl-thumbnail==12.6.3 # via -r requirements/base.txt +sqlparse==0.3.1 # via -r requirements/base.txt, django +wcwidth==0.2.5 # via pytest +webencodings==0.5.1 # via -r requirements/base.txt, bleach +zipp==1.2.0 # via importlib-metadata diff --git a/requirements/tox.in b/requirements/tox.in new file mode 100644 index 000000000..9a2869477 --- /dev/null +++ b/requirements/tox.in @@ -0,0 +1,4 @@ +# Used for tests +-c constraints.txt + +tox diff --git a/requirements/tox.txt b/requirements/tox.txt new file mode 100644 index 000000000..53bf2cd40 --- /dev/null +++ b/requirements/tox.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# make upgrade +# +appdirs==1.4.4 # via virtualenv +distlib==0.3.1 # via virtualenv +filelock==3.0.12 # via tox, virtualenv +importlib-metadata==1.7.0 # via pluggy, tox, virtualenv +importlib-resources==3.0.0 # via virtualenv +packaging==20.4 # via tox +pluggy==0.13.1 # via tox +py==1.9.0 # via tox +pyparsing==2.4.7 # via packaging +six==1.15.0 # via packaging, tox, virtualenv +toml==0.10.1 # via tox +tox==3.16.1 # via -r requirements/tox.in +virtualenv==20.0.25 # via tox +zipp==1.2.0 # via importlib-metadata, importlib-resources diff --git a/requirements/travis.in b/requirements/travis.in new file mode 100644 index 000000000..3dbd7b0c3 --- /dev/null +++ b/requirements/travis.in @@ -0,0 +1,4 @@ +# Requirements for running tests in Travis +-c constraints.txt + +-r tox.txt diff --git a/requirements/travis.txt b/requirements/travis.txt new file mode 100644 index 000000000..498f5d84d --- /dev/null +++ b/requirements/travis.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# make upgrade +# +appdirs==1.4.4 # via -r requirements/tox.txt, virtualenv +distlib==0.3.1 # via -r requirements/tox.txt, virtualenv +filelock==3.0.12 # via -r requirements/tox.txt, tox, virtualenv +importlib-metadata==1.7.0 # via -r requirements/tox.txt, pluggy, tox, virtualenv +importlib-resources==3.0.0 # via -r requirements/tox.txt, virtualenv +packaging==20.4 # via -r requirements/tox.txt, tox +pluggy==0.13.1 # via -r requirements/tox.txt, tox +py==1.9.0 # via -r requirements/tox.txt, tox +pyparsing==2.4.7 # via -r requirements/tox.txt, packaging +six==1.15.0 # via -r requirements/tox.txt, packaging, tox, virtualenv +toml==0.10.1 # via -r requirements/tox.txt, tox +tox==3.16.1 # via -r requirements/tox.txt +virtualenv==20.0.25 # via -r requirements/tox.txt, tox +zipp==1.2.0 # via -r requirements/tox.txt, importlib-metadata, importlib-resources diff --git a/setup.py b/setup.py index 2db1eeec5..4072b9409 100644 --- a/setup.py +++ b/setup.py @@ -62,7 +62,7 @@ def is_requirement(line): setup( name="django-wiki", - version="0.0.27", + version="0.1.0", author="Benjamin Bach", author_email="benjamin@overtag.dk", description=("A wiki system written for the Django framework."), @@ -79,10 +79,8 @@ def is_requirement(line): 'Framework :: Django', 'Intended Audience :: Developers', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.8', 'Framework :: Django', 'Framework :: Django :: 2.2', diff --git a/testproject/pytest.ini b/testproject/pytest.ini new file mode 100644 index 000000000..810e29de6 --- /dev/null +++ b/testproject/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +DJANGO_SETTINGS_MODULE = testproject.settings +python_files = tests.py test_*.py *_tests.py diff --git a/tox.ini b/tox.ini new file mode 100644 index 000000000..8533b7248 --- /dev/null +++ b/tox.ini @@ -0,0 +1,10 @@ +[tox] +envlist = {py35,py38}-django{22} + +[testenv] +deps = + django22: -r requirements/django.txt + -r{toxinidir}/requirements/test.txt +changedir={toxinidir}/testproject/ +commands = + pytest --cov wiki --cov django_notify diff --git a/wiki/tests.py b/wiki/tests.py index 1ddecbc38..e8574f90d 100644 --- a/wiki/tests.py +++ b/wiki/tests.py @@ -1,18 +1,134 @@ -""" -This file demonstrates writing tests using the unittest module. These will pass -when you run "manage.py test". +from django.contrib.auth.models import User +from django.core.cache import cache +from django.urls import reverse +from django.test import TestCase +from django.test.client import Client +from wiki.models import Article, ArticleRevision, URLPath +import pprint +import re -Replace this with more appropriate tests for your application. -""" -from __future__ import absolute_import +class InitialWebClientTest(TestCase): + """Tests by the dummy web client, with manual creating the root article.""" -from django.test import TestCase + def setUp(self): + User.objects.create_superuser('admin', 'nobody@example.com', 'secret') + self.c = c = Client() + c.login(username='admin', password='secret') + def test_root_article(self): + """Test redirecting to /create-root/, creating the root article and a simple markup.""" + c = self.c + response = c.get(reverse('wiki:root')) # url '/' + self.assertRedirects(response, reverse('wiki:root_create')) # url '/create-root/' + response = c.post(reverse('wiki:root_create'), + {'content': 'test heading h1\n====\n', 'title': 'Wiki Test'}) + self.assertRedirects(response, reverse('wiki:root')) + response = c.get(reverse('wiki:root')) + self.assertContains(response, 'test heading h1') -class SimpleTest(TestCase): - def test_basic_addition(self): - """ - Tests that 1 + 1 always equals 2. + +class WebClientTest(TestCase): + """Tests by the dummy web client.""" + def setUp(self): + User.objects.create_superuser('admin', 'nobody@example.com', 'secret') + self.c = c = Client() + c.login(username='admin', password='secret') + response = self.c.post(reverse('wiki:root_create'), {'content': 'root article content', 'title': 'Root Article'}) + self.example_data = { + 'content': 'The modified text', + 'current_revision': '1', + 'preview': '1', + 'summary': 'why edited', + 'title': 'wiki test'} + + def tearDown(self): + # clear Article cache before the next test + cache.clear() + + def get_by_path(self, path): + """Get the article response for the path. + Example: self.get_by_path("Level1/Slug2/").title """ - self.assertEqual(1 + 1, 2) + return self.c.get(reverse('wiki:get', kwargs={'path': path})) + + def test_preview_save(self): + """Test edit preview, edit save and messages.""" + c = self.c + # test preview + response = c.post(reverse('wiki:preview', kwargs={'path': ''}), self.example_data) # url: '/_preview/' + self.assertContains(response, 'The modified text') + # test save and messages + example2 = self.example_data.copy() + example2['content'] = 'Something 2' + response = c.post(reverse('wiki:edit', kwargs={'path': ''}), example2) + message = c.cookies['messages'].value if 'messages' in c.cookies else None + self.assertRedirects(response, reverse('wiki:root')) + response = c.get(reverse('wiki:root')) + self.assertContains(response, 'Something 2') + self.assertTrue('successfully added' in message) + + def test_redirect_create(self): + """Test that redirects to create if the slug is unknown.""" + response = self.get_by_path('Unknown/') + self.assertRedirects(response, reverse('wiki:create', kwargs={'path': ''}) + '?slug=Unknown') + + def test_cleared_cache(self): + """Test the article cache is cleared after delete.""" + # That bug is tested by one individual test, otherwise it could be + # revealed only by sequence of tests in some particular order + c = self.c + response = c.post(reverse('wiki:create', kwargs={'path': ''}), + {'title': 'Test cache', 'slug': 'TestCache', 'content': 'Content 1'}) + self.assertRedirects(response, reverse('wiki:get', kwargs={'path': 'TestCache/'})) + self.assertContains(self.get_by_path('TestCache/'), 'Content 1') + response = c.post(reverse('wiki:delete', kwargs={'path': 'TestCache/'}), + {'confirm': 'on', 'purge': 'on', 'revision': '2'}) + self.assertRedirects(response, reverse('wiki:get', kwargs={'path': ''})) + response = c.post(reverse('wiki:create', kwargs={'path': ''}), + {'title': 'Test cache', 'slug': 'TestCache', 'content': 'Content 2'}) + self.assertRedirects(response, reverse('wiki:get', kwargs={'path': 'TestCache/'})) + # test the cache + self.assertContains(self.get_by_path('TestCache/'), 'Content 2') + + def test_article_list_update(self): + """Test automatic adding and removing the new article to/from article_list.""" + c = self.c + root_data = {'content': '[article_list depth:2]', 'current_revision': '1', 'preview': '1', 'title': 'Root Article'} + response = c.post(reverse('wiki:edit', kwargs={'path': ''}), root_data) + self.assertRedirects(response, reverse('wiki:root')) + # verify the new article is added to article_list + response = c.post(reverse('wiki:create', kwargs={'path': ''}), + {'title': 'Sub Article 1', 'slug': 'SubArticle1'}) + self.assertRedirects(response, reverse('wiki:get', kwargs={'path': 'SubArticle1/'})) + self.assertContains(self.get_by_path(''), 'Sub Article 1') + self.assertContains(self.get_by_path(''), 'SubArticle1/') + # verify the deleted article is removed from article_list + response = c.post(reverse('wiki:delete', kwargs={'path': 'SubArticle1/'}), + {'confirm': 'on', 'purge': 'on', 'revision': '3'}) + message = c.cookies['messages'].value if 'messages' in c.cookies else None + self.assertRedirects(response, reverse('wiki:get', kwargs={'path': ''})) + self.assertTrue('This article together with all its contents are now completely gone' in message) + self.assertNotContains(self.get_by_path(''), 'Sub Article 1') + + def test_revision_conflict(self): + """Test the warning if the same article is beeing edited concurrently.""" + c = self.c + response = c.post(reverse('wiki:edit', kwargs={'path': ''}), self.example_data) + self.assertRedirects(response, reverse('wiki:root')) + response = c.post(reverse('wiki:edit', kwargs={'path': ''}), self.example_data) + self.assertContains(response, 'While you were editing, someone else changed the revision.') + + def test_nested_create(self): + c = self.c + response = c.post(reverse('wiki:create', kwargs={'path': ''}), + {'title': 'Level 1', 'slug': 'Level1', 'content': 'Content level 1'}) + self.assertRedirects(response, reverse('wiki:get', kwargs={'path': 'Level1/'})) + response = c.post(reverse('wiki:create', kwargs={'path': 'Level1/'}), + {'title': 'test', 'slug': 'Test', 'content': 'Content on level 2'}) + self.assertRedirects(response, reverse('wiki:get', kwargs={'path': 'Level1/Test/'})) + response = c.post(reverse('wiki:create', kwargs={'path': ''}), + {'title': 'test', 'slug': 'Test', 'content': 'Other content on level 1'}) + self.assertRedirects(response, reverse('wiki:get', kwargs={'path': 'Test/'})) + self.assertContains(self.get_by_path('Test/'), 'Other content on level 1') + self.assertContains(self.get_by_path('Level1/Test/'), 'Content') # on level 2') From f6928ac185f0b8a98a958f0a24de6aa6cb2bdc8f Mon Sep 17 00:00:00 2001 From: edX requirements bot Date: Fri, 21 Aug 2020 14:50:17 -0400 Subject: [PATCH 57/80] This pull request was generated by the cleanup-python-code Jenkins job, which ran python -m edx_repo_tools.modernize_openedx_yaml --path=openedx.yaml The following packages were installed: git+https://github.com/edx/repo-tools.git@4ac2ffde02b6df4bf633f7b88966137d71233bb1. --- openedx.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openedx.yaml b/openedx.yaml index 44b38b82d..097eb65fd 100644 --- a/openedx.yaml +++ b/openedx.yaml @@ -2,11 +2,9 @@ # https://open-edx-proposals.readthedocs.io/en/latest/oep-0002-bp-repo-metadata.html#specification: nick: django-wiki -supporting_teams: - - platform-core tags: - core - library oeps: - oep-7: True - oep-18: True + oep-7: true + oep-18: true From 13d9617afe3fb77000acbf8b46254e9d69810246 Mon Sep 17 00:00:00 2001 From: Alex Dusenbery Date: Tue, 15 Sep 2020 11:56:38 -0400 Subject: [PATCH 58/80] Make reverse() resilient to 0-length args. --- setup.py | 2 +- wiki/models/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 4072b9409..b550659ca 100644 --- a/setup.py +++ b/setup.py @@ -62,7 +62,7 @@ def is_requirement(line): setup( name="django-wiki", - version="0.1.0", + version="0.1.1", author="Benjamin Bach", author_email="benjamin@overtag.dk", description=("A wiki system written for the Django framework."), diff --git a/wiki/models/__init__.py b/wiki/models/__init__.py index b18f349d5..a3e127ac3 100644 --- a/wiki/models/__init__.py +++ b/wiki/models/__init__.py @@ -75,7 +75,7 @@ def reverse(*args, **kwargs): return the result of calling reverse._transform_url(reversed_url) for every url in the wiki namespace. """ - if isinstance(args[0], string_types) and args[0].startswith('wiki:'): + if args and isinstance(args[0], string_types) and args[0].startswith('wiki:'): url_kwargs = kwargs.get('kwargs', {}) path = url_kwargs.get('path', False) # If a path is supplied then discard the article_id From afc510091d3a86647006be9d4df0a3731c550bb5 Mon Sep 17 00:00:00 2001 From: Aarif Date: Thu, 14 Jan 2021 17:34:57 +0500 Subject: [PATCH 59/80] dropped support for python 3.5 and some refactoring --- .github/workflows/ci.yml | 35 +++++++ .travis.yml | 12 +-- Makefile | 4 +- django_notify/admin.py | 2 - django_notify/decorators.py | 3 - django_notify/migrations/0001_initial.py | 3 - django_notify/models.py | 33 +++---- django_notify/settings.py | 8 +- django_notify/tests.py | 1 - django_notify/urls.py | 7 +- django_notify/views.py | 4 - docs/conf.py | 18 ++-- requirements/base.txt | 51 +++++++--- requirements/{travis.in => ci.in} | 0 requirements/ci.txt | 50 ++++++++++ requirements/docs.txt | 77 ++++++++++----- requirements/pip.in | 0 requirements/pip.txt | 6 ++ requirements/pip_tools.txt | 7 +- requirements/test.txt | 97 ++++++++++++++----- requirements/tox.txt | 40 +++++--- requirements/travis.txt | 20 ---- setup.py | 16 ++- testproject/manage.py | 1 - testproject/testproject/settings.py | 3 - testproject/testproject/urls.py | 2 - testproject/testproject/wsgi.py | 1 - testproject/vmanage.py | 1 - tox.ini | 8 +- wiki/admin.py | 8 +- wiki/apps.py | 2 - wiki/conf/__init__.py | 1 - wiki/conf/settings.py | 3 - wiki/core/__init__.py | 2 - wiki/core/compat.py | 2 +- wiki/core/diff.py | 2 - wiki/core/exceptions.py | 1 - wiki/core/extensions.py | 2 - wiki/core/http.py | 2 - wiki/core/permissions.py | 2 - wiki/core/plugins/base.py | 10 +- wiki/core/plugins/loader.py | 2 - wiki/core/plugins/registry.py | 5 +- wiki/core/processors.py | 2 - wiki/decorators.py | 4 - wiki/editors/__init__.py | 2 - wiki/editors/base.py | 2 - wiki/editors/markitup.py | 10 +- wiki/forms.py | 95 +++++++++--------- wiki/management/commands/wikiviz.py | 3 +- wiki/managers.py | 2 - wiki/middleware.py | 4 +- wiki/migrations/0001_initial.py | 9 +- .../0002_remove_article_subscription.py | 3 - wiki/migrations/0003_ip_address_conv.py | 3 - wiki/migrations/0004_increase_slug_size.py | 3 - .../0005_remove_attachments_and_images.py | 2 - wiki/migrations/0006_auto_20200110_1003.py | 2 - wiki/models/__init__.py | 7 +- wiki/models/article.py | 50 +++++----- wiki/models/pluginbase.py | 23 ++--- wiki/models/urlpath.py | 31 +++--- wiki/plugins/attachments/__init__.py | 2 - wiki/plugins/attachments/admin.py | 2 - wiki/plugins/attachments/forms.py | 11 +-- .../attachments/markdown_extensions.py | 8 +- .../attachments/migrations/0001_initial.py | 3 - wiki/plugins/attachments/models.py | 20 ++-- wiki/plugins/attachments/settings.py | 2 - wiki/plugins/attachments/views.py | 49 +++++----- wiki/plugins/attachments/wiki_plugin.py | 7 +- wiki/plugins/help/models.py | 2 - wiki/plugins/help/tests.py | 1 - wiki/plugins/help/wiki_plugin.py | 3 - wiki/plugins/images/__init__.py | 1 - wiki/plugins/images/admin.py | 3 +- wiki/plugins/images/forms.py | 17 ++-- wiki/plugins/images/markdown_extensions.py | 1 - .../plugins/images/migrations/0001_initial.py | 4 - wiki/plugins/images/models.py | 15 ++- wiki/plugins/images/settings.py | 1 - .../images/templatetags/wiki_images_tags.py | 1 - wiki/plugins/images/views.py | 7 +- wiki/plugins/images/wiki_plugin.py | 14 ++- wiki/plugins/links/mdx/djangowikilinks.py | 3 +- wiki/plugins/links/mdx/urlize.py | 1 - wiki/plugins/links/models.py | 1 - wiki/plugins/links/tests.py | 1 - wiki/plugins/links/views.py | 1 - wiki/plugins/links/wiki_plugin.py | 2 - wiki/plugins/notifications/__init__.py | 1 - wiki/plugins/notifications/forms.py | 9 +- .../notifications/migrations/0001_initial.py | 4 - wiki/plugins/notifications/models.py | 10 +- wiki/plugins/notifications/tests.py | 1 - wiki/plugins/notifications/wiki_plugin.py | 1 - wiki/templatetags/wiki_tags.py | 2 - wiki/urls.py | 3 - wiki/views/accounts.py | 10 +- wiki/views/article.py | 74 +++++++------- wiki/views/mixins.py | 4 +- 101 files changed, 557 insertions(+), 556 deletions(-) create mode 100644 .github/workflows/ci.yml rename requirements/{travis.in => ci.in} (100%) create mode 100644 requirements/ci.txt create mode 100644 requirements/pip.in create mode 100644 requirements/pip.txt delete mode 100644 requirements/travis.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..5b486340f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: Python CI + +on: + push: + branches: [master] + pull_request: + branches: + - '**' + +jobs: + run_tests: + name: Tests + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-20.04] + python-version: ['3.8'] + toxenv: [django22, django30, django31] + steps: + - uses: actions/checkout@v1 + - name: setup python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install pip + run: pip install -r requirements/pip.txt + + - name: Install Dependencies + run: pip install -r requirements/ci.txt + + - name: Run Tests + env: + TOXENV: ${{ matrix.toxenv }} + run: tox diff --git a/.travis.yml b/.travis.yml index b8baa020e..c04bbe4bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,11 @@ language: python python: - - 3.5 - 3.8 install: - - pip install -r requirements/travis.txt -matrix: - - python: 3.5 - env: TOXENV=py35-django22 - - python: 3.8 - env: TOXENV=py38-django22 + - pip install -r requirements/ci.txt +env: + - TOXENV=py38-django22 + - TOXENV=py38-django30 + - TOXENV=py38-django31 script: - tox diff --git a/Makefile b/Makefile index 73686e09c..0e257852a 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,13 @@ upgrade: export CUSTOM_COMPILE_COMMAND=make upgrade upgrade: pip install -q -r requirements/pip_tools.txt + pip-compile --upgrade --allow-unsafe --rebuild -o requirements/pip.txt requirements/pip.in pip-compile --upgrade -o requirements/pip_tools.txt requirements/pip_tools.in pip-compile --upgrade -o requirements/base.txt requirements/base.in pip-compile --upgrade -o requirements/docs.txt requirements/docs.in pip-compile --upgrade -o requirements/test.txt requirements/test.in pip-compile --upgrade -o requirements/tox.txt requirements/tox.in - pip-compile --upgrade -o requirements/travis.txt requirements/travis.in + pip-compile --upgrade -o requirements/ci.txt requirements/ci.in # Let tox control the Django version for tests - grep -e "^django==" requirements/base.txt > requirements/django.txt sed '/^[dD]jango==/d' requirements/test.txt > requirements/test.tmp mv requirements/test.tmp requirements/test.txt diff --git a/django_notify/admin.py b/django_notify/admin.py index 19eaee01e..1abc3a9d3 100644 --- a/django_notify/admin.py +++ b/django_notify/admin.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from django.contrib import admin from django_notify import models diff --git a/django_notify/decorators.py b/django_notify/decorators.py index 6d87ccca9..518881ace 100644 --- a/django_notify/decorators.py +++ b/django_notify/decorators.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - import json from django.contrib.auth.decorators import login_required diff --git a/django_notify/migrations/0001_initial.py b/django_notify/migrations/0001_initial.py index 4f9972b60..4a77c0d96 100644 --- a/django_notify/migrations/0001_initial.py +++ b/django_notify/migrations/0001_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - import django.db.models.deletion from django.conf import settings from django.db import migrations, models diff --git a/django_notify/models.py b/django_notify/models.py index 89ffd1f57..0687bb3c1 100644 --- a/django_notify/models.py +++ b/django_notify/models.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.db import models @@ -15,9 +12,9 @@ class NotificationType(models.Model): """ Notification types are added on-the-fly by the applications adding new notifications""" - key = models.CharField(max_length=128, primary_key=True, verbose_name=_(u'unique key'), + key = models.CharField(max_length=128, primary_key=True, verbose_name=_('unique key'), unique=True) - label = models.CharField(max_length=128, verbose_name=_(u'verbose name'), + label = models.CharField(max_length=128, verbose_name=_('verbose name'), blank=True, null=True) content_type = models.ForeignKey(ContentType, blank=True, null=True, on_delete=models.CASCADE) @@ -27,23 +24,23 @@ def __unicode__(self): class Meta: app_label = 'django_notify' db_table = settings.DB_TABLE_PREFIX + '_notificationtype' - verbose_name = _(u'type') - verbose_name_plural = _(u'types') + verbose_name = _('type') + verbose_name_plural = _('types') class Settings(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) - interval = models.SmallIntegerField(choices=settings.INTERVALS, verbose_name=_(u'interval'), + interval = models.SmallIntegerField(choices=settings.INTERVALS, verbose_name=_('interval'), default=settings.INTERVALS_DEFAULT) def __unicode__(self): - return _(u"Settings for %s") % self.user.username + return _("Settings for %s") % self.user.username class Meta: app_label = 'django_notify' db_table = settings.DB_TABLE_PREFIX + '_settings' - verbose_name = _(u'settings') - verbose_name_plural = _(u'settings') + verbose_name = _('settings') + verbose_name_plural = _('settings') class Subscription(models.Model): @@ -51,7 +48,7 @@ class Subscription(models.Model): settings = models.ForeignKey(Settings, on_delete=models.CASCADE) notification_type = models.ForeignKey(NotificationType, on_delete=models.CASCADE) object_id = models.CharField(max_length=64, null=True, blank=True, - help_text=_(u'Leave this blank to subscribe to any kind of object')) + help_text=_('Leave this blank to subscribe to any kind of object')) send_emails = models.BooleanField(default=True) def __unicode__(self): @@ -60,14 +57,14 @@ def __unicode__(self): class Meta: app_label = 'django_notify' db_table = settings.DB_TABLE_PREFIX + '_subscription' - verbose_name = _(u'subscription') - verbose_name_plural = _(u'subscriptions') + verbose_name = _('subscription') + verbose_name_plural = _('subscriptions') class Notification(models.Model): subscription = models.ForeignKey(Subscription, null=True, blank=True, on_delete=models.SET_NULL) message = models.TextField() - url = models.URLField(blank=True, null=True, verbose_name=_(u'link for notification')) + url = models.URLField(blank=True, null=True, verbose_name=_('link for notification')) is_viewed = models.BooleanField(default=False) is_emailed = models.BooleanField(default=False) created = models.DateTimeField(auto_now_add=True) @@ -106,8 +103,8 @@ def __unicode__(self): class Meta: app_label = 'django_notify' db_table = settings.DB_TABLE_PREFIX + '_notification' - verbose_name = _(u'notification') - verbose_name_plural = _(u'notifications') + verbose_name = _('notification') + verbose_name_plural = _('notifications') def notify(message, key, target_object=None, url=None): @@ -133,7 +130,7 @@ def notify(message, key, target_object=None, url=None): if target_object: if not isinstance(target_object, Model): - raise TypeError(_(u"You supplied a target_object that's not an instance of a django Model.")) + raise TypeError(_("You supplied a target_object that's not an instance of a django Model.")) object_id = target_object.id else: object_id = None diff --git a/django_notify/settings.py b/django_notify/settings.py index 70dcc232b..334d42341 100644 --- a/django_notify/settings.py +++ b/django_notify/settings.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from django.conf import settings as django_settings _ = lambda x: x @@ -22,9 +20,9 @@ WEEKLY = 7*24-1 INTERVALS = getattr(django_settings, "NOTIFY_INTERVALS", - [(INSTANTLY, _(u'instantly')), - (DAILY, _(u'daily')), - (WEEKLY, _(u'weekly'))]) + [(INSTANTLY, _('instantly')), + (DAILY, _('daily')), + (WEEKLY, _('weekly'))]) INTERVALS_DEFAULT = INSTANTLY diff --git a/django_notify/tests.py b/django_notify/tests.py index 1ddecbc38..06eeec547 100644 --- a/django_notify/tests.py +++ b/django_notify/tests.py @@ -5,7 +5,6 @@ Replace this with more appropriate tests for your application. """ -from __future__ import absolute_import from django.test import TestCase diff --git a/django_notify/urls.py b/django_notify/urls.py index 03516ddb3..90c318303 100644 --- a/django_notify/urls.py +++ b/django_notify/urls.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - from django.conf.urls import url from django_notify import views @@ -8,8 +5,8 @@ urlpatterns = [ url('^json/get/$', views.get_notifications, name='json_get', kwargs={}), url('^json/mark-read/$', views.mark_read, name='json_mark_read_base', kwargs={}), - url('^json/mark-read/(\d+)/$', views.mark_read, name='json_mark_read', kwargs={}), - url('^goto/(?P\d+)/$', views.goto, name='goto', kwargs={}), + url(r'^json/mark-read/(\d+)/$', views.mark_read, name='json_mark_read', kwargs={}), + url(r'^goto/(?P\d+)/$', views.goto, name='goto', kwargs={}), url('^goto/$', views.goto, name='goto_base', kwargs={}), ] diff --git a/django_notify/views.py b/django_notify/views.py index e4866dcf5..99fda5d8a 100644 --- a/django_notify/views.py +++ b/django_notify/views.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import - from django.contrib.auth.decorators import login_required from django.shortcuts import get_object_or_404, redirect diff --git a/docs/conf.py b/docs/conf.py index 9456640de..4e5504f41 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # django-wiki documentation build configuration file, created by # sphinx-quickstart on Mon Jul 23 16:13:51 2012. @@ -11,7 +10,6 @@ # All configuration values have a default; values that are commented out # serve to show the default. -from __future__ import absolute_import import os import sys @@ -43,8 +41,8 @@ master_doc = 'index' # General information about the project. -project = u'django-wiki' -copyright = u'2012, Benjamin Bach' +project = 'django-wiki' +copyright = '2012, Benjamin Bach' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -186,8 +184,8 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'django-wiki.tex', u'django-wiki Documentation', - u'Benjamin Bach', 'manual'), + ('index', 'django-wiki.tex', 'django-wiki Documentation', + 'Benjamin Bach', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -216,8 +214,8 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'django-wiki', u'django-wiki Documentation', - [u'Benjamin Bach'], 1) + ('index', 'django-wiki', 'django-wiki Documentation', + ['Benjamin Bach'], 1) ] # If true, show URL addresses after external links. @@ -230,8 +228,8 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'django-wiki', u'django-wiki Documentation', - u'Benjamin Bach', 'django-wiki', 'One line description of project.', + ('index', 'django-wiki', 'django-wiki Documentation', + 'Benjamin Bach', 'django-wiki', 'One line description of project.', 'Miscellaneous'), ] diff --git a/requirements/base.txt b/requirements/base.txt index 1380df0b9..6f6947ec2 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -4,17 +4,40 @@ # # make upgrade # -bleach==3.1.5 # via -r requirements/base.in -django-classy-tags==1.0.0 # via django-sekizai -django-js-asset==1.2.2 # via django-mptt -django-mptt==0.11.0 # via -r requirements/base.in -django-sekizai==1.1.0 # via -r requirements/base.in -django==2.2.14 # via -c requirements/constraints.txt, -r requirements/base.in, django-classy-tags, django-mptt, django-sekizai -markdown==2.6.11 # via -c requirements/constraints.txt, -r requirements/base.in -packaging==20.4 # via bleach -pyparsing==2.4.7 # via packaging -pytz==2020.1 # via django -six==1.15.0 # via -r requirements/base.in, bleach, django-classy-tags, django-sekizai, packaging -sorl-thumbnail==12.6.3 # via -r requirements/base.in -sqlparse==0.3.1 # via django -webencodings==0.5.1 # via bleach +bleach==3.2.1 + # via -r requirements/base.in +django-classy-tags==2.0.0 + # via django-sekizai +django-js-asset==1.2.2 + # via django-mptt +django-mptt==0.11.0 + # via -r requirements/base.in +django-sekizai==2.0.0 + # via -r requirements/base.in +django==2.2.17 + # via + # -c requirements/constraints.txt + # -r requirements/base.in + # django-classy-tags + # django-mptt + # django-sekizai +markdown==2.6.11 + # via + # -c requirements/constraints.txt + # -r requirements/base.in +packaging==20.8 + # via bleach +pyparsing==2.4.7 + # via packaging +pytz==2020.5 + # via django +six==1.15.0 + # via + # -r requirements/base.in + # bleach +sorl-thumbnail==12.7.0 + # via -r requirements/base.in +sqlparse==0.4.1 + # via django +webencodings==0.5.1 + # via bleach diff --git a/requirements/travis.in b/requirements/ci.in similarity index 100% rename from requirements/travis.in rename to requirements/ci.in diff --git a/requirements/ci.txt b/requirements/ci.txt new file mode 100644 index 000000000..72b43e872 --- /dev/null +++ b/requirements/ci.txt @@ -0,0 +1,50 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# make upgrade +# +appdirs==1.4.4 + # via + # -r requirements/tox.txt + # virtualenv +distlib==0.3.1 + # via + # -r requirements/tox.txt + # virtualenv +filelock==3.0.12 + # via + # -r requirements/tox.txt + # tox + # virtualenv +packaging==20.8 + # via + # -r requirements/tox.txt + # tox +pluggy==0.13.1 + # via + # -r requirements/tox.txt + # tox +py==1.10.0 + # via + # -r requirements/tox.txt + # tox +pyparsing==2.4.7 + # via + # -r requirements/tox.txt + # packaging +six==1.15.0 + # via + # -r requirements/tox.txt + # tox + # virtualenv +toml==0.10.2 + # via + # -r requirements/tox.txt + # tox +tox==3.21.1 + # via -r requirements/tox.txt +virtualenv==20.3.1 + # via + # -r requirements/tox.txt + # tox diff --git a/requirements/docs.txt b/requirements/docs.txt index 34bc60738..cf7b7199c 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -4,31 +4,58 @@ # # make upgrade # -alabaster==0.7.12 # via sphinx -babel==2.8.0 # via sphinx -certifi==2020.6.20 # via requests -chardet==3.0.4 # via requests -docutils==0.16 # via sphinx -edx-sphinx-theme==1.5.0 # via -r requirements/docs.in -idna==2.10 # via requests -imagesize==1.2.0 # via sphinx -jinja2==2.11.2 # via sphinx -markupsafe==1.1.1 # via jinja2 -packaging==20.4 # via sphinx -pygments==2.6.1 # via sphinx -pyparsing==2.4.7 # via packaging -pytz==2020.1 # via babel -requests==2.24.0 # via sphinx -six==1.15.0 # via edx-sphinx-theme, packaging -snowballstemmer==2.0.0 # via sphinx -sphinx==3.1.1 # via -r requirements/docs.in, edx-sphinx-theme -sphinxcontrib-applehelp==1.0.2 # via sphinx -sphinxcontrib-devhelp==1.0.2 # via sphinx -sphinxcontrib-htmlhelp==1.0.3 # via sphinx -sphinxcontrib-jsmath==1.0.1 # via sphinx -sphinxcontrib-qthelp==1.0.3 # via sphinx -sphinxcontrib-serializinghtml==1.1.4 # via sphinx -urllib3==1.25.9 # via requests +alabaster==0.7.12 + # via sphinx +babel==2.9.0 + # via sphinx +certifi==2020.12.5 + # via requests +chardet==4.0.0 + # via requests +docutils==0.16 + # via sphinx +edx-sphinx-theme==1.6.0 + # via -r requirements/docs.in +idna==2.10 + # via requests +imagesize==1.2.0 + # via sphinx +jinja2==2.11.2 + # via sphinx +markupsafe==1.1.1 + # via jinja2 +packaging==20.8 + # via sphinx +pygments==2.7.4 + # via sphinx +pyparsing==2.4.7 + # via packaging +pytz==2020.5 + # via babel +requests==2.25.1 + # via sphinx +six==1.15.0 + # via edx-sphinx-theme +snowballstemmer==2.0.0 + # via sphinx +sphinx==3.4.3 + # via + # -r requirements/docs.in + # edx-sphinx-theme +sphinxcontrib-applehelp==1.0.2 + # via sphinx +sphinxcontrib-devhelp==1.0.2 + # via sphinx +sphinxcontrib-htmlhelp==1.0.3 + # via sphinx +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-qthelp==1.0.3 + # via sphinx +sphinxcontrib-serializinghtml==1.1.4 + # via sphinx +urllib3==1.26.2 + # via requests # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/requirements/pip.in b/requirements/pip.in new file mode 100644 index 000000000..e69de29bb diff --git a/requirements/pip.txt b/requirements/pip.txt new file mode 100644 index 000000000..7a338ac77 --- /dev/null +++ b/requirements/pip.txt @@ -0,0 +1,6 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# make upgrade +# diff --git a/requirements/pip_tools.txt b/requirements/pip_tools.txt index 279019f74..53310ee5c 100644 --- a/requirements/pip_tools.txt +++ b/requirements/pip_tools.txt @@ -4,9 +4,10 @@ # # make upgrade # -click==7.1.2 # via pip-tools -pip-tools==5.2.1 # via -r requirements/pip_tools.in -six==1.15.0 # via pip-tools +click==7.1.2 + # via pip-tools +pip-tools==5.5.0 + # via -r requirements/pip_tools.in # The following packages are considered to be unsafe in a requirements file: # pip diff --git a/requirements/test.txt b/requirements/test.txt index eb6cdf418..930e68721 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -4,28 +4,75 @@ # # make upgrade # -attrs==19.3.0 # via pytest -bleach==3.1.5 # via -r requirements/base.txt -coverage==5.1 # via pytest-cov -django-classy-tags==1.0.0 # via -r requirements/base.txt, django-sekizai -django-js-asset==1.2.2 # via -r requirements/base.txt, django-mptt -django-mptt==0.11.0 # via -r requirements/base.txt -django-sekizai==1.1.0 # via -r requirements/base.txt -importlib-metadata==1.7.0 # via pluggy, pytest -markdown==2.6.11 # via -c requirements/constraints.txt, -r requirements/base.txt -more-itertools==8.4.0 # via pytest -packaging==20.4 # via -r requirements/base.txt, bleach, pytest -pathlib2==2.3.5 # via pytest -pluggy==0.13.1 # via pytest -py==1.9.0 # via pytest -pyparsing==2.4.7 # via -r requirements/base.txt, packaging -pytest-cov==2.10.0 # via -r requirements/test.in -pytest-django==3.9.0 # via -r requirements/test.in -pytest==5.4.3 # via -r requirements/test.in, pytest-cov, pytest-django -pytz==2020.1 # via -r requirements/base.txt, django -six==1.15.0 # via -r requirements/base.txt, bleach, django-classy-tags, django-sekizai, packaging, pathlib2 -sorl-thumbnail==12.6.3 # via -r requirements/base.txt -sqlparse==0.3.1 # via -r requirements/base.txt, django -wcwidth==0.2.5 # via pytest -webencodings==0.5.1 # via -r requirements/base.txt, bleach -zipp==1.2.0 # via importlib-metadata +attrs==20.3.0 + # via pytest +bleach==3.2.1 + # via -r requirements/base.txt +coverage==5.3.1 + # via pytest-cov +django-classy-tags==2.0.0 + # via + # -r requirements/base.txt + # django-sekizai +django-js-asset==1.2.2 + # via + # -r requirements/base.txt + # django-mptt +django-mptt==0.11.0 + # via -r requirements/base.txt +django-sekizai==2.0.0 + # via -r requirements/base.txt + # via + # -c requirements/constraints.txt + # -r requirements/base.txt + # django-classy-tags + # django-mptt + # django-sekizai +iniconfig==1.1.1 + # via pytest +markdown==2.6.11 + # via + # -c requirements/constraints.txt + # -r requirements/base.txt +packaging==20.8 + # via + # -r requirements/base.txt + # bleach + # pytest +pluggy==0.13.1 + # via pytest +py==1.10.0 + # via pytest +pyparsing==2.4.7 + # via + # -r requirements/base.txt + # packaging +pytest-cov==2.10.1 + # via -r requirements/test.in +pytest-django==4.1.0 + # via -r requirements/test.in +pytest==6.2.1 + # via + # -r requirements/test.in + # pytest-cov + # pytest-django +pytz==2020.5 + # via + # -r requirements/base.txt + # django +six==1.15.0 + # via + # -r requirements/base.txt + # bleach +sorl-thumbnail==12.7.0 + # via -r requirements/base.txt +sqlparse==0.4.1 + # via + # -r requirements/base.txt + # django +toml==0.10.2 + # via pytest +webencodings==0.5.1 + # via + # -r requirements/base.txt + # bleach diff --git a/requirements/tox.txt b/requirements/tox.txt index 53bf2cd40..4214a5e52 100644 --- a/requirements/tox.txt +++ b/requirements/tox.txt @@ -4,17 +4,29 @@ # # make upgrade # -appdirs==1.4.4 # via virtualenv -distlib==0.3.1 # via virtualenv -filelock==3.0.12 # via tox, virtualenv -importlib-metadata==1.7.0 # via pluggy, tox, virtualenv -importlib-resources==3.0.0 # via virtualenv -packaging==20.4 # via tox -pluggy==0.13.1 # via tox -py==1.9.0 # via tox -pyparsing==2.4.7 # via packaging -six==1.15.0 # via packaging, tox, virtualenv -toml==0.10.1 # via tox -tox==3.16.1 # via -r requirements/tox.in -virtualenv==20.0.25 # via tox -zipp==1.2.0 # via importlib-metadata, importlib-resources +appdirs==1.4.4 + # via virtualenv +distlib==0.3.1 + # via virtualenv +filelock==3.0.12 + # via + # tox + # virtualenv +packaging==20.8 + # via tox +pluggy==0.13.1 + # via tox +py==1.10.0 + # via tox +pyparsing==2.4.7 + # via packaging +six==1.15.0 + # via + # tox + # virtualenv +toml==0.10.2 + # via tox +tox==3.21.1 + # via -r requirements/tox.in +virtualenv==20.3.1 + # via tox diff --git a/requirements/travis.txt b/requirements/travis.txt deleted file mode 100644 index 498f5d84d..000000000 --- a/requirements/travis.txt +++ /dev/null @@ -1,20 +0,0 @@ -# -# This file is autogenerated by pip-compile -# To update, run: -# -# make upgrade -# -appdirs==1.4.4 # via -r requirements/tox.txt, virtualenv -distlib==0.3.1 # via -r requirements/tox.txt, virtualenv -filelock==3.0.12 # via -r requirements/tox.txt, tox, virtualenv -importlib-metadata==1.7.0 # via -r requirements/tox.txt, pluggy, tox, virtualenv -importlib-resources==3.0.0 # via -r requirements/tox.txt, virtualenv -packaging==20.4 # via -r requirements/tox.txt, tox -pluggy==0.13.1 # via -r requirements/tox.txt, tox -py==1.9.0 # via -r requirements/tox.txt, tox -pyparsing==2.4.7 # via -r requirements/tox.txt, packaging -six==1.15.0 # via -r requirements/tox.txt, packaging, tox, virtualenv -toml==0.10.1 # via -r requirements/tox.txt, tox -tox==3.16.1 # via -r requirements/tox.txt -virtualenv==20.0.25 # via -r requirements/tox.txt, tox -zipp==1.2.0 # via -r requirements/tox.txt, importlib-metadata, importlib-resources diff --git a/setup.py b/setup.py index b550659ca..dcba30e68 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - import os from setuptools import find_packages, setup @@ -55,17 +52,17 @@ def is_requirement(line): packages = find_packages() -package_data = dict( - (package_name, template_patterns) +package_data = { + package_name: template_patterns for package_name in packages -) +} setup( name="django-wiki", - version="0.1.1", + version="1.0.0", author="Benjamin Bach", author_email="benjamin@overtag.dk", - description=("A wiki system written for the Django framework."), + description="A wiki system written for the Django framework.", license="GPLv3", keywords="django wiki markdown", packages=find_packages(exclude=["testproject", "testproject.*"]), @@ -80,10 +77,11 @@ def is_requirement(line): 'Intended Audience :: Developers', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.8', 'Framework :: Django', 'Framework :: Django :: 2.2', + 'Framework :: Django :: 3.0', + 'Framework :: Django :: 3.1', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Software Development', 'Topic :: Software Development :: Libraries :: Application Frameworks', diff --git a/testproject/manage.py b/testproject/manage.py index 8c9f710b6..06c89afba 100755 --- a/testproject/manage.py +++ b/testproject/manage.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -from __future__ import absolute_import import os import sys diff --git a/testproject/testproject/settings.py b/testproject/testproject/settings.py index 7a3ec92e5..d70c498d4 100644 --- a/testproject/testproject/settings.py +++ b/testproject/testproject/settings.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - from os import path as os_path PROJECT_PATH = os_path.abspath(os_path.split(__file__)[0]) diff --git a/testproject/testproject/urls.py b/testproject/testproject/urls.py index 2e1cc9485..eaa47c094 100644 --- a/testproject/testproject/urls.py +++ b/testproject/testproject/urls.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from django.conf.urls import include, url from django.conf import settings from django.contrib.staticfiles.urls import staticfiles_urlpatterns diff --git a/testproject/testproject/wsgi.py b/testproject/testproject/wsgi.py index cba768928..fc4f30d7d 100644 --- a/testproject/testproject/wsgi.py +++ b/testproject/testproject/wsgi.py @@ -13,7 +13,6 @@ framework. """ -from __future__ import absolute_import import os import sys diff --git a/testproject/vmanage.py b/testproject/vmanage.py index 2a831bb22..68fe4826a 100755 --- a/testproject/vmanage.py +++ b/testproject/vmanage.py @@ -1,5 +1,4 @@ #!virtualenv/bin/python -from __future__ import absolute_import import os import sys diff --git a/tox.ini b/tox.ini index 8533b7248..bcda8d32c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,12 @@ [tox] -envlist = {py35,py38}-django{22} +envlist = py38-django{22,30,31} [testenv] deps = - django22: -r requirements/django.txt - -r{toxinidir}/requirements/test.txt + django22: Django>=2.2,<2.3 + django30: Django>=3.0,<3.1 + django31: Django>=3.1,<3.2 + -r{toxinidir}/requirements/test.txt changedir={toxinidir}/testproject/ commands = pytest --cov wiki --cov django_notify diff --git a/wiki/admin.py b/wiki/admin.py index 6dcbc2d53..c8789338c 100644 --- a/wiki/admin.py +++ b/wiki/admin.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from django import forms from django.contrib import admin from django.contrib.contenttypes.admin import GenericTabularInline @@ -22,7 +20,7 @@ class Meta: fields = '__all__' def __init__(self, *args, **kwargs): - super(ArticleRevisionForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) EditorClass = editors.getEditorClass() editor = editors.getEditor() self.fields['content'].widget = editor.get_admin_widget() @@ -54,7 +52,7 @@ class Meta: fields = '__all__' def __init__(self, *args, **kwargs): - super(ArticleForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if self.instance.pk: revisions = models.ArticleRevision.objects.filter(article=self.instance) self.fields['current_revision'].queryset = revisions @@ -77,7 +75,7 @@ class URLPathAdmin(MPTTModelAdmin): def get_created(self, instance): return instance.article.created - get_created.short_description = _(u'created') + get_created.short_description = _('created') admin.site.register(models.URLPath, URLPathAdmin) diff --git a/wiki/apps.py b/wiki/apps.py index 51417b761..42fb43f76 100644 --- a/wiki/apps.py +++ b/wiki/apps.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, unicode_literals - from django.apps import AppConfig from django.utils.translation import ugettext_lazy as _ diff --git a/wiki/conf/__init__.py b/wiki/conf/__init__.py index 40a96afc6..e69de29bb 100644 --- a/wiki/conf/__init__.py +++ b/wiki/conf/__init__.py @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/wiki/conf/settings.py b/wiki/conf/settings.py index 178727b1f..e4d8c8bfe 100644 --- a/wiki/conf/settings.py +++ b/wiki/conf/settings.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - from django.conf import settings as django_settings from django.urls import reverse_lazy diff --git a/wiki/core/__init__.py b/wiki/core/__init__.py index de5adcdf3..4a73ce8ac 100644 --- a/wiki/core/__init__.py +++ b/wiki/core/__init__.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - import bleach import markdown diff --git a/wiki/core/compat.py b/wiki/core/compat.py index 8beeb5f79..ad73030e4 100644 --- a/wiki/core/compat.py +++ b/wiki/core/compat.py @@ -3,7 +3,7 @@ # so we restore that version. # When support for Django < 1.11 is dropped, we should look at using the # new template based rendering, at which point this probably won't be needed at all. -class BuildAttrsCompat(object): +class BuildAttrsCompat: def build_attrs_compat(self, extra_attrs=None, **kwargs): "Helper function for building an attribute dictionary." attrs = self.attrs.copy() diff --git a/wiki/core/diff.py b/wiki/core/diff.py index e19bdac18..4c5fd6f18 100644 --- a/wiki/core/diff.py +++ b/wiki/core/diff.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - import difflib diff --git a/wiki/core/exceptions.py b/wiki/core/exceptions.py index a95264532..442b85c9d 100644 --- a/wiki/core/exceptions.py +++ b/wiki/core/exceptions.py @@ -1,4 +1,3 @@ - # If no root URL is found, we raise this... class NoRootURL(Exception): pass diff --git a/wiki/core/extensions.py b/wiki/core/extensions.py index 9fa6a9385..5d19e893c 100644 --- a/wiki/core/extensions.py +++ b/wiki/core/extensions.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from markdown.extensions import Extension from wiki.core.processors import AnchorTagProcessor diff --git a/wiki/core/http.py b/wiki/core/http.py index 3ad4160c3..c93833046 100644 --- a/wiki/core/http.py +++ b/wiki/core/http.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - import mimetypes import os from datetime import datetime diff --git a/wiki/core/permissions.py b/wiki/core/permissions.py index a953194db..307869c5c 100644 --- a/wiki/core/permissions.py +++ b/wiki/core/permissions.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from wiki.conf import settings diff --git a/wiki/core/plugins/base.py b/wiki/core/plugins/base.py index eca464da6..1c4e2cbbe 100644 --- a/wiki/core/plugins/base.py +++ b/wiki/core/plugins/base.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from django.utils.translation import ugettext_lazy as _ @@ -13,7 +11,7 @@ plugin's models. """ -class BasePlugin(object): +class BasePlugin: """Plugins should inherit from this""" # Must fill in! slug = None @@ -38,13 +36,13 @@ class RenderMedia: js = [] css = {} -class PluginSidebarFormMixin(object): +class PluginSidebarFormMixin: def get_usermessage(self): pass -class PluginSettingsFormMixin(object): - settings_form_headline = _(u'Settings for plugin') +class PluginSettingsFormMixin: + settings_form_headline = _('Settings for plugin') settings_order = 1 settings_write_access = False diff --git a/wiki/core/plugins/loader.py b/wiki/core/plugins/loader.py index 6e61aa2a4..122acfc80 100644 --- a/wiki/core/plugins/loader.py +++ b/wiki/core/plugins/loader.py @@ -1,11 +1,9 @@ -# -*- coding: utf-8 -*- """ Credits to ojii, functions get_module and load are from: https://github.com/ojii/django-load. Thanks for the technique! """ -from __future__ import absolute_import, print_function from importlib import import_module diff --git a/wiki/core/plugins/registry.py b/wiki/core/plugins/registry.py index 6b3afb817..beae89c74 100644 --- a/wiki/core/plugins/registry.py +++ b/wiki/core/plugins/registry.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - from importlib import import_module import six @@ -23,7 +20,7 @@ def register(PluginClass): settings_form = getattr(PluginClass, 'settings_form', None) if settings_form: - if isinstance(settings_form, six.string_types): + if isinstance(settings_form, str): klassname = settings_form.split(".")[-1] modulename = ".".join(settings_form.split(".")[:-1]) form_module = import_module(modulename) diff --git a/wiki/core/processors.py b/wiki/core/processors.py index 216c01f15..d1b00a8e6 100644 --- a/wiki/core/processors.py +++ b/wiki/core/processors.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from markdown.treeprocessors import Treeprocessor diff --git a/wiki/decorators.py b/wiki/decorators.py index e90c4634d..810f0fe1c 100644 --- a/wiki/decorators.py +++ b/wiki/decorators.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - import json from django.conf import settings as django_settings @@ -10,7 +7,6 @@ from django.template.context import RequestContext from django.template.loader import render_to_string from django.urls import reverse -from six.moves import filter from wiki.core.exceptions import NoRootURL diff --git a/wiki/editors/__init__.py b/wiki/editors/__init__.py index d1a3d88eb..a03d34e48 100644 --- a/wiki/editors/__init__.py +++ b/wiki/editors/__init__.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from django.urls import get_callable from wiki.conf import settings diff --git a/wiki/editors/base.py b/wiki/editors/base.py index 94687e39f..c7e589dc8 100644 --- a/wiki/editors/base.py +++ b/wiki/editors/base.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from django import forms diff --git a/wiki/editors/markitup.py b/wiki/editors/markitup.py index 67c7c51ba..399ab8379 100644 --- a/wiki/editors/markitup.py +++ b/wiki/editors/markitup.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from django import forms from django.forms.utils import flatatt from django.utils.encoding import force_text @@ -18,12 +16,12 @@ def __init__(self, attrs=None): 'rows': '10', 'cols': '40',} if attrs: default_attrs.update(attrs) - super(MarkItUpAdminWidget, self).__init__(default_attrs) + super().__init__(default_attrs) def render(self, name, value, attrs=None): if value is None: value = '' final_attrs = self.build_attrs_compat(attrs, name=name) - return mark_safe(u'%s' % (flatatt(final_attrs), + return mark_safe('%s' % (flatatt(final_attrs), conditional_escape(force_text(value)))) @@ -34,12 +32,12 @@ def __init__(self, attrs=None): 'rows': '10', 'cols': '40',} if attrs: default_attrs.update(attrs) - super(MarkItUpWidget, self).__init__(default_attrs) + super().__init__(default_attrs) def render(self, name, value, attrs=None, renderer=None): if value is None: value = '' final_attrs = self.build_attrs_compat(attrs, name=name) - return mark_safe(u'
%s
' % (flatatt(final_attrs), + return mark_safe('
%s
' % (flatatt(final_attrs), conditional_escape(force_text(value)))) class MarkItUp(BaseEditor): diff --git a/wiki/forms.py b/wiki/forms.py index ffd11e296..c6a6e382f 100644 --- a/wiki/forms.py +++ b/wiki/forms.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function - from itertools import chain from django import forms @@ -37,19 +34,19 @@ def check_spam(self, current_revision, request): class CreateRootForm(forms.Form): - title = forms.CharField(label=_(u'Title'), help_text=_(u'Initial title of the article. May be overridden with revision titles.')) - content = forms.CharField(label=_(u'Type in some contents'), - help_text=_(u'This is just the initial contents of your article. After creating it, you can use more complex features like adding plugins, meta data, related articles etc...'), + title = forms.CharField(label=_('Title'), help_text=_('Initial title of the article. May be overridden with revision titles.')) + content = forms.CharField(label=_('Type in some contents'), + help_text=_('This is just the initial contents of your article. After creating it, you can use more complex features like adding plugins, meta data, related articles etc...'), required=False, widget=getEditor().get_widget()) #@UndefinedVariable class EditForm(forms.Form): - title = forms.CharField(label=_(u'Title'),) - content = forms.CharField(label=_(u'Contents'), + title = forms.CharField(label=_('Title'),) + content = forms.CharField(label=_('Contents'), required=False, widget=getEditor().get_widget()) #@UndefinedVariable - summary = forms.CharField(label=_(u'Summary'), help_text=_(u'Give a short reason for your edit, which will be stated in the revision log.'), + summary = forms.CharField(label=_('Summary'), help_text=_('Give a short reason for your edit, which will be stated in the revision log.'), required=False) current_revision = forms.IntegerField(required=False, widget=forms.HiddenInput()) @@ -87,7 +84,7 @@ def __init__(self, current_revision, *args, **kwargs): kwargs['initial'] = initial - super(EditForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def clean_title(self): title = strip_tags(self.cleaned_data['title']) @@ -98,9 +95,9 @@ def clean(self): if self.no_clean or self.preview: return cd if not str(self.initial_revision.id) == str(self.presumed_revision): - raise forms.ValidationError(_(u'While you were editing, someone else changed the revision. Your contents have been automatically merged with the new contents. Please review the text below.')) + raise forms.ValidationError(_('While you were editing, someone else changed the revision. Your contents have been automatically merged with the new contents. Please review the text below.')) if cd['title'] == self.initial_revision.title and cd['content'] == self.initial_revision.content: - raise forms.ValidationError(_(u'No changes made. Nothing to save.')) + raise forms.ValidationError(_('No changes made. Nothing to save.')) return cd @@ -138,10 +135,10 @@ class SelectWidgetBootstrap(BuildAttrsCompat, forms.Select): """) def __init__(self, attrs={'class': 'btn-group pull-left btn-group-form'}, choices=()): self.noscript_widget = forms.Select(attrs={}, choices=choices) - super(SelectWidgetBootstrap, self).__init__(attrs, choices) + super().__init__(attrs, choices) def __setattr__(self, k, value): - super(SelectWidgetBootstrap, self).__setattr__(k, value) + super().__setattr__(k, value) if k != 'attrs': self.noscript_widget.__setattr__(k, value) @@ -162,57 +159,57 @@ def render(self, name, value, attrs=None, choices=()): """""" % {'attrs': flatatt(final_attrs), 'options':self.render_options(choices, [value]), - 'label': _(u'Select an option'), + 'label': _('Select an option'), 'name': name, 'js': SelectWidgetBootstrap.js, 'noscript': self.noscript_widget.render(name, value, {}, choices)} ] - return mark_safe(u'\n'.join(output)) + return mark_safe('\n'.join(output)) def render_option(self, selected_choices, option_value, option_label): option_value = force_text(option_value) - selected_html = (option_value in selected_choices) and u' selected="selected"' or '' - return u'
' % ( + selected_html = (option_value in selected_choices) and ' selected="selected"' or '' + return '
  • %s
  • ' % ( escape(option_value), selected_html, conditional_escape(force_text(option_label))) def render_options(self, choices, selected_choices): # Normalize to strings. - selected_choices = set([force_text(v) for v in selected_choices]) + selected_choices = {force_text(v) for v in selected_choices} output = [] for option_value, option_label in chain(self.choices, choices): if isinstance(option_label, (list, tuple)): - output.append(u'
  • ' % escape(force_text(option_value))) + output.append('
  • ' % escape(force_text(option_value))) for option in option_label: output.append(self.render_option(selected_choices, *option)) else: output.append(self.render_option(selected_choices, option_value, option_label)) - return u'\n'.join(output) + return '\n'.join(output) class TextInputPrepend(forms.TextInput): def __init__(self, *args, **kwargs): self.prepend = kwargs.pop('prepend', "") - super(TextInputPrepend, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def render(self, *args, **kwargs): - html = super(TextInputPrepend, self).render(*args, **kwargs) + html = super().render(*args, **kwargs) return mark_safe('
    %s%s
    ' % (self.prepend, html)) class CreateForm(forms.Form): def __init__(self, urlpath_parent, *args, **kwargs): - super(CreateForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.urlpath_parent = urlpath_parent - title = forms.CharField(label=_(u'Title'),) - slug = forms.SlugField(label=_(u'Slug'), help_text=_(u"This will be the address where your article can be found. Use only alphanumeric characters and - or _. Note that you cannot change the slug after creating the article."), + title = forms.CharField(label=_('Title'),) + slug = forms.SlugField(label=_('Slug'), help_text=_("This will be the address where your article can be found. Use only alphanumeric characters and - or _. Note that you cannot change the slug after creating the article."), max_length=models.URLPath.SLUG_MAX_LENGTH) - content = forms.CharField(label=_(u'Contents'), + content = forms.CharField(label=_('Contents'), required=False, widget=getEditor().get_widget()) #@UndefinedVariable - summary = forms.CharField(label=_(u'Summary'), help_text=_(u"Write a brief message for the article's history log."), + summary = forms.CharField(label=_('Summary'), help_text=_("Write a brief message for the article's history log."), required=False) def clean_title(self): @@ -222,7 +219,7 @@ def clean_title(self): def clean_slug(self): slug = self.cleaned_data['slug'] if slug.startswith("_"): - raise forms.ValidationError(_(u'A slug may not begin with an underscore.')) + raise forms.ValidationError(_('A slug may not begin with an underscore.')) if settings.URL_CASE_SENSITIVE: already_existing_slug = models.URLPath.objects.filter(slug=slug, parent=self.urlpath_parent) @@ -231,9 +228,9 @@ def clean_slug(self): if already_existing_slug: already_urlpath = already_existing_slug[0] if already_urlpath.article and already_urlpath.article.current_revision.deleted: - raise forms.ValidationError(_(u'A deleted article with slug "%s" already exists.') % already_urlpath.slug) + raise forms.ValidationError(_('A deleted article with slug "%s" already exists.') % already_urlpath.slug) else: - raise forms.ValidationError(_(u'A slug named "%s" already exists.') % already_urlpath.slug) + raise forms.ValidationError(_('A slug named "%s" already exists.') % already_urlpath.slug) return slug @@ -243,42 +240,42 @@ class DeleteForm(forms.Form): def __init__(self, *args, **kwargs): self.article = kwargs.pop('article') self.has_children = kwargs.pop('has_children') - super(DeleteForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) confirm = forms.BooleanField(required=False, - label=_(u'Yes, I am sure')) + label=_('Yes, I am sure')) purge = forms.BooleanField(widget=HiddenInput(), required=False, - label=_(u'Purge'), - help_text=_(u'Purge the article: Completely remove it (and all its contents) with no undo. Purging is a good idea if you want to free the slug such that users can create new articles in its place.')) + label=_('Purge'), + help_text=_('Purge the article: Completely remove it (and all its contents) with no undo. Purging is a good idea if you want to free the slug such that users can create new articles in its place.')) revision = forms.ModelChoiceField(models.ArticleRevision.objects.all(), widget=HiddenInput(), required=False) def clean(self): cd = self.cleaned_data if not cd['confirm']: - raise forms.ValidationError(_(u'You are not sure enough!')) + raise forms.ValidationError(_('You are not sure enough!')) if cd['revision'] != self.article.current_revision: - raise forms.ValidationError(_(u'While you tried to delete this article, it was modified. TAKE CARE!')) + raise forms.ValidationError(_('While you tried to delete this article, it was modified. TAKE CARE!')) return cd class PermissionsForm(PluginSettingsFormMixin, forms.ModelForm): - locked = forms.BooleanField(label=_(u'Lock article'), help_text=_(u'Deny all users access to edit this article.'), + locked = forms.BooleanField(label=_('Lock article'), help_text=_('Deny all users access to edit this article.'), required=False) - settings_form_headline = _(u'Permissions') + settings_form_headline = _('Permissions') settings_order = 5 settings_write_access = False - owner_username = forms.CharField(required=False, label=_(u'Owner'), - help_text=_(u'Enter the username of the owner.')) - group = forms.ModelChoiceField(models.Group.objects.all(), empty_label=_(u'(none)'), + owner_username = forms.CharField(required=False, label=_('Owner'), + help_text=_('Enter the username of the owner.')) + group = forms.ModelChoiceField(models.Group.objects.all(), empty_label=_('(none)'), required=False) if settings.USE_BOOTSTRAP_SELECT_WIDGET: group.widget= SelectWidgetBootstrap() - recursive = forms.BooleanField(label=_(u'Inherit permissions'), help_text=_(u'Check here to apply the above permissions recursively to articles under this one.'), + recursive = forms.BooleanField(label=_('Inherit permissions'), help_text=_('Check here to apply the above permissions recursively to articles under this one.'), required=False) def get_usermessage(self): @@ -293,7 +290,7 @@ def __init__(self, article, request, *args, **kwargs): self.request = request kwargs['instance'] = article kwargs['initial'] = {'locked': article.current_revision.locked} - super(PermissionsForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.can_change_groups = False self.can_assign = False @@ -324,7 +321,7 @@ def clean_owner_username(self): try: user = User.objects.get(username=username) except models.User.DoesNotExist: - raise forms.ValidationError(_(u'No user with that username')) + raise forms.ValidationError(_('No user with that username')) else: user = None else: @@ -332,7 +329,7 @@ def clean_owner_username(self): return user def save(self, commit=True): - article = super(PermissionsForm, self).save(commit=False) + article = super().save(commit=False) article.owner = self.cleaned_data['owner_username'] if not self.can_change_groups: article.group = self.article.group @@ -346,14 +343,14 @@ def save(self, commit=True): revision = models.ArticleRevision() revision.inherit_predecessor(self.article) revision.set_from_request(self.request) - revision.automatic_log = _(u'Article locked for editing') + revision.automatic_log = _('Article locked for editing') revision.locked = True self.article.add_revision(revision) elif not self.cleaned_data['locked'] and article.current_revision.locked: revision = models.ArticleRevision() revision.inherit_predecessor(self.article) revision.set_from_request(self.request) - revision.automatic_log = _(u'Article unlocked for editing') + revision.automatic_log = _('Article unlocked for editing') revision.locked = False self.article.add_revision(revision) @@ -366,4 +363,4 @@ class Meta: class DirFilterForm(forms.Form): - query = forms.CharField(label=_(u'Filter'), widget=forms.TextInput(attrs={'class': 'search-query'})) + query = forms.CharField(label=_('Filter'), widget=forms.TextInput(attrs={'class': 'search-query'})) diff --git a/wiki/management/commands/wikiviz.py b/wiki/management/commands/wikiviz.py index 9e6c53563..7062d5b9e 100644 --- a/wiki/management/commands/wikiviz.py +++ b/wiki/management/commands/wikiviz.py @@ -43,7 +43,6 @@ -e, --inheritance show inheritance arrows. """ -from __future__ import absolute_import, print_function import os import sys @@ -205,7 +204,7 @@ def add_attributes(field): t = type(field).__name__ if isinstance(field, (OneToOneField, ForeignKey)): - t += " ({0})".format(field.rel.field_name) + t += f" ({field.rel.field_name})" # TODO: ManyToManyField, GenericRelation model['fields'].append({ diff --git a/wiki/managers.py b/wiki/managers.py index 1c6c0f5e2..3876c15ee 100644 --- a/wiki/managers.py +++ b/wiki/managers.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from django.db import models from django.db.models import Q from django.db.models.query import EmptyQuerySet, QuerySet diff --git a/wiki/middleware.py b/wiki/middleware.py index 803eb8a7c..29185f4b4 100644 --- a/wiki/middleware.py +++ b/wiki/middleware.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - import importlib import threading @@ -23,7 +21,7 @@ class _RequestCache(threading.local): A thread-local for storing the per-request cache. """ def __init__(self): - super(_RequestCache, self).__init__() + super().__init__() self.data = {} self.request = None diff --git a/wiki/migrations/0001_initial.py b/wiki/migrations/0001_initial.py index 00915fcfc..8b52ac707 100644 --- a/wiki/migrations/0001_initial.py +++ b/wiki/migrations/0001_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - import mptt.fields from django.conf import settings from django.db import migrations, models @@ -222,7 +219,7 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='urlpath', - unique_together=set([('site', 'parent', 'slug')]), + unique_together={('site', 'parent', 'slug')}, ), migrations.AddField( model_name='revisionpluginrevision', @@ -280,7 +277,7 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='articlerevision', - unique_together=set([('article', 'revision_number')]), + unique_together={('article', 'revision_number')}, ), migrations.AddField( model_name='articleplugin', @@ -290,7 +287,7 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='articleforobject', - unique_together=set([('content_type', 'object_id')]), + unique_together={('content_type', 'object_id')}, ), migrations.AddField( model_name='article', diff --git a/wiki/migrations/0002_remove_article_subscription.py b/wiki/migrations/0002_remove_article_subscription.py index be93ad704..55d1295c2 100644 --- a/wiki/migrations/0002_remove_article_subscription.py +++ b/wiki/migrations/0002_remove_article_subscription.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - from django.db import migrations, models diff --git a/wiki/migrations/0003_ip_address_conv.py b/wiki/migrations/0003_ip_address_conv.py index 22612fb9c..5d6e6cba1 100644 --- a/wiki/migrations/0003_ip_address_conv.py +++ b/wiki/migrations/0003_ip_address_conv.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - from django.db import migrations, models diff --git a/wiki/migrations/0004_increase_slug_size.py b/wiki/migrations/0004_increase_slug_size.py index ef5038f0d..01e491dc8 100644 --- a/wiki/migrations/0004_increase_slug_size.py +++ b/wiki/migrations/0004_increase_slug_size.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - from django.db import migrations, models diff --git a/wiki/migrations/0005_remove_attachments_and_images.py b/wiki/migrations/0005_remove_attachments_and_images.py index c44b9105d..308664e38 100644 --- a/wiki/migrations/0005_remove_attachments_and_images.py +++ b/wiki/migrations/0005_remove_attachments_and_images.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.5 on 2017-09-07 19:03 -from __future__ import absolute_import, unicode_literals from django.db import migrations diff --git a/wiki/migrations/0006_auto_20200110_1003.py b/wiki/migrations/0006_auto_20200110_1003.py index fd82ade41..4fb7a1662 100644 --- a/wiki/migrations/0006_auto_20200110_1003.py +++ b/wiki/migrations/0006_auto_20200110_1003.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.27 on 2020-01-10 10:03 -from __future__ import unicode_literals from django.db import migrations, models diff --git a/wiki/models/__init__.py b/wiki/models/__init__.py index a3e127ac3..eccd2383d 100644 --- a/wiki/models/__init__.py +++ b/wiki/models/__init__.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - from django.conf import settings as django_settings from django.core.exceptions import ImproperlyConfigured from six import string_types, text_type @@ -75,7 +72,7 @@ def reverse(*args, **kwargs): return the result of calling reverse._transform_url(reversed_url) for every url in the wiki namespace. """ - if args and isinstance(args[0], string_types) and args[0].startswith('wiki:'): + if args and isinstance(args[0], str) and args[0].startswith('wiki:'): url_kwargs = kwargs.get('kwargs', {}) path = url_kwargs.get('path', False) # If a path is supplied then discard the article_id @@ -94,7 +91,7 @@ def reverse(*args, **kwargs): # Now we redefine reverse method -reverse_lazy = lazy(reverse, text_type) +reverse_lazy = lazy(reverse, str) urls.reverse = reverse urls.reverse_lazy = reverse_lazy diff --git a/wiki/models/article.py b/wiki/models/article.py index 9991341a1..901e86275 100644 --- a/wiki/models/article.py +++ b/wiki/models/article.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - import bleach from django.contrib.auth.models import Group, User from django.contrib.contenttypes import fields @@ -23,27 +20,27 @@ class Article(models.Model): objects = managers.ArticleManager() current_revision = models.OneToOneField('ArticleRevision', - verbose_name=_(u'current revision'), + verbose_name=_('current revision'), blank=True, null=True, related_name='current_set', - help_text=_(u'The revision being displayed for this article. If you need to do a roll-back, simply change the value of this field.'), on_delete=models.CASCADE + help_text=_('The revision being displayed for this article. If you need to do a roll-back, simply change the value of this field.'), on_delete=models.CASCADE ) - created = models.DateTimeField(auto_now_add=True, verbose_name=_(u'created')) - modified = models.DateTimeField(auto_now=True, verbose_name=_(u'modified'), - help_text=_(u'Article properties last modified')) + created = models.DateTimeField(auto_now_add=True, verbose_name=_('created')) + modified = models.DateTimeField(auto_now=True, verbose_name=_('modified'), + help_text=_('Article properties last modified')) owner = models.ForeignKey(User, verbose_name=_('owner'), blank=True, null=True, related_name='owned_articles', - help_text=_(u'The owner of the article, usually the creator. The owner always has both read and write access.'), on_delete=models.CASCADE) + help_text=_('The owner of the article, usually the creator. The owner always has both read and write access.'), on_delete=models.CASCADE) group = models.ForeignKey(Group, verbose_name=_('group'), blank=True, null=True, - help_text=_(u'Like in a UNIX file system, permissions can be given to a user according to group membership. Groups are handled through the Django auth system.'), on_delete=models.CASCADE) + help_text=_('Like in a UNIX file system, permissions can be given to a user according to group membership. Groups are handled through the Django auth system.'), on_delete=models.CASCADE) - group_read = models.BooleanField(default=True, verbose_name=_(u'group read access')) - group_write = models.BooleanField(default=True, verbose_name=_(u'group write access')) - other_read = models.BooleanField(default=True, verbose_name=_(u'others read access')) - other_write = models.BooleanField(default=True, verbose_name=_(u'others write access')) + group_read = models.BooleanField(default=True, verbose_name=_('group read access')) + group_write = models.BooleanField(default=True, verbose_name=_('group write access')) + other_read = models.BooleanField(default=True, verbose_name=_('others read access')) + other_write = models.BooleanField(default=True, verbose_name=_('others write access')) # TODO: Do not use kwargs, it can lead to dangerous situations with bad # permission checking patterns. Also, since there are no other keywords, @@ -96,8 +93,7 @@ def can_assign(self, user): def descendant_objects(self): """NB! This generator is expensive, so use it with care!!""" for obj in self.articleforobject_set.filter(is_mptt=True): - for descendant in obj.content_object.get_descendants(): - yield descendant + yield from obj.content_object.get_descendants() def get_children(self, max_num=None, user_can_read=None, **kwargs): """NB! This generator is expensive, so use it with care!!""" @@ -174,7 +170,7 @@ def get_for_object(cls, obj): def __unicode__(self): if self.current_revision: return self.current_revision.title - return _(u'Article without content (%(id)d)') % {'id': self.id} + return _('Article without content (%(id)d)') % {'id': self.id} class Meta: permissions = ( @@ -210,8 +206,8 @@ class ArticleForObject(models.Model): is_mptt = models.BooleanField(default=False, editable=False) class Meta: - verbose_name = _(u'Article for object') - verbose_name_plural = _(u'Articles for object') + verbose_name = _('Article for object') + verbose_name_plural = _('Articles for object') # Do not allow several objects unique_together = ('content_type', 'object_id') @@ -220,7 +216,7 @@ class BaseRevisionMixin(models.Model): """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.""" - revision_number = models.IntegerField(editable=False, verbose_name=_(u'revision number')) + revision_number = models.IntegerField(editable=False, verbose_name=_('revision number')) user_message = models.TextField(blank=True) automatic_log = models.TextField(blank=True, editable=False) @@ -246,8 +242,8 @@ class BaseRevisionMixin(models.Model): # NOTE! The semantics of these fields are not related to the revision itself # but the actual related object. If the latest revision says "deleted=True" then # the related object should be regarded as deleted. - deleted = models.BooleanField(verbose_name=_(u'deleted'), default=False) - locked = models.BooleanField(verbose_name=_(u'locked'), default=False) + deleted = models.BooleanField(verbose_name=_('deleted'), default=False) + locked = models.BooleanField(verbose_name=_('locked'), default=False) def set_from_request(self, request): if request.user.is_authenticated: @@ -266,15 +262,15 @@ class ArticleRevision(BaseRevisionMixin, models.Model): copy, do NEVER create m2m relationships.""" article = models.ForeignKey('Article', on_delete=models.CASCADE, - verbose_name=_(u'article')) + verbose_name=_('article')) # This is where the content goes, with whatever markup language is used - content = models.TextField(blank=True, verbose_name=_(u'article contents')) + content = models.TextField(blank=True, verbose_name=_('article contents')) # This title is automatically set from either the article's title or # the last used revision... - title = models.CharField(max_length=512, verbose_name=_(u'article title'), - null=False, blank=False, help_text=_(u'Each revision contains a title field that must be filled out, even if the title has not changed')) + title = models.CharField(max_length=512, verbose_name=_('article title'), + null=False, blank=False, help_text=_('Each revision contains a title field that must be filled out, even if the title has not changed')) # TODO: # Allow a revision to redirect to another *article*. This @@ -316,7 +312,7 @@ def save(self, *args, **kwargs): self.revision_number = 1 self.clean_data() - super(ArticleRevision, self).save(*args, **kwargs) + super().save(*args, **kwargs) if not self.article.current_revision: # If I'm saved from Django admin, then article.current_revision is me! diff --git a/wiki/models/pluginbase.py b/wiki/models/pluginbase.py index f78519998..d97d8ed70 100644 --- a/wiki/models/pluginbase.py +++ b/wiki/models/pluginbase.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - from django.db import models from django.db.models import signals from django.utils.translation import ugettext_lazy as _ @@ -39,7 +36,7 @@ class ArticlePlugin(models.Model): properties in the future...""" article = models.ForeignKey(Article, on_delete=models.CASCADE, - verbose_name=_(u"article")) + verbose_name=_("article")) deleted = models.BooleanField(default=False) @@ -75,8 +72,8 @@ class ReusablePlugin(ArticlePlugin): # The article on which the plugin was originally created. # Used to apply permissions. ArticlePlugin.article.on_delete=models.SET_NULL - ArticlePlugin.article.verbose_name=_(u'original article') - ArticlePlugin.article.help_text=_(u'Permissions are inherited from this article') + ArticlePlugin.article.verbose_name=_('original article') + ArticlePlugin.article.help_text=_('Permissions are inherited from this article') ArticlePlugin.article.null = True ArticlePlugin.article.blank = True @@ -101,7 +98,7 @@ def save(self, *args, **kwargs): if articles.count() == 0: self.article = articles[0] - super(ReusablePlugin, self).save(*args, **kwargs) + super().save(*args, **kwargs) class SimplePluginCreateError(Exception): pass @@ -128,13 +125,13 @@ class YourPlugin(SimplePlugin): article_revision = models.ForeignKey(ArticleRevision, on_delete=models.CASCADE) def __init__(self, *args, **kwargs): - super(SimplePlugin, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if not self.id and not 'article' in kwargs: raise SimplePluginCreateError("Keyword argument 'article' expected.") self.article = kwargs['article'] def get_logmessage(self): - return _(u"A plugin was changed") + return _("A plugin was changed") def save(self, *args, **kwargs): if not self.id: @@ -146,7 +143,7 @@ def save(self, *args, **kwargs): new_revision.save() self.article_revision = new_revision - super(SimplePlugin, self).save(*args, **kwargs) + super().save(*args, **kwargs) class RevisionPlugin(ArticlePlugin): @@ -159,9 +156,9 @@ class RevisionPlugin(ArticlePlugin): """ # The current revision of this plugin, if any! current_revision = models.OneToOneField('RevisionPluginRevision', - verbose_name=_(u'current revision'), + verbose_name=_('current revision'), blank=True, null=True, related_name='plugin_set', - help_text=_(u'The revision being displayed for this plugin.' + help_text=_('The revision being displayed for this plugin.' 'If you need to do a roll-back, simply change the value of this field.'), on_delete=models.CASCADE ) @@ -213,7 +210,7 @@ def save(self, *args, **kwargs): except RevisionPluginRevision.DoesNotExist: self.revision_number = 1 - super(RevisionPluginRevision, self).save(*args, **kwargs) + super().save(*args, **kwargs) if not self.plugin.current_revision: # If I'm saved from Django admin, then plugin.current_revision is me! diff --git a/wiki/models/urlpath.py b/wiki/models/urlpath.py index 5f717b73f..88562425b 100644 --- a/wiki/models/urlpath.py +++ b/wiki/models/urlpath.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function - import logging from django.contrib.contenttypes import fields @@ -40,13 +37,13 @@ class URLPath(MPTTModel): # 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')) + verbose_name=_('Cache lookup value for articles')) # The slug is constructed from course key and will in practice be much shorter then 255 characters # since course keys are capped at 65 characters in the Studio (https://openedx.atlassian.net/browse/TNL-889). SLUG_MAX_LENGTH = 255 - slug = models.SlugField(verbose_name=_(u'slug'), null=True, blank=True, + slug = models.SlugField(verbose_name=_('slug'), null=True, blank=True, max_length=SLUG_MAX_LENGTH) site = models.ForeignKey(Site, on_delete=models.CASCADE) parent = TreeForeignKey('self', null=True, blank=True, related_name='children', on_delete=models.CASCADE) @@ -55,7 +52,7 @@ def __init__(self, *args, **kwargs): pass # Fixed in django-mptt 0.5.3 #self._tree_manager = URLPath.objects - return super(URLPath, self).__init__(*args, **kwargs) + return super().__init__(*args, **kwargs) @property def cached_ancestors(self): @@ -136,29 +133,29 @@ class MPTTMeta: def __unicode__(self): path = self.path - return path if path else ugettext(u"(root)") + return path if path else ugettext("(root)") def save(self, *args, **kwargs): - super(URLPath, self).save(*args, **kwargs) + super().save(*args, **kwargs) def delete(self, *args, **kwargs): assert not (self.parent and self.get_children()), "You cannot delete a root article with children." - super(URLPath, self).delete(*args, **kwargs) + super().delete(*args, **kwargs) class Meta: - verbose_name = _(u'URL path') - verbose_name_plural = _(u'URL paths') + verbose_name = _('URL path') + verbose_name_plural = _('URL paths') unique_together = ('site', 'parent', 'slug') def clean(self, *args, **kwargs): if self.slug and not self.parent: - raise ValidationError(_(u'Sorry but you cannot have a root article with a slug.')) + raise ValidationError(_('Sorry but you cannot have a root article with a slug.')) if not self.slug and self.parent: - raise ValidationError(_(u'A non-root note must always have a slug.')) + raise ValidationError(_('A non-root note must always have a slug.')) if not self.parent: if URLPath.objects.root_nodes().filter(site=self.site).exclude(id=self.id): - raise ValidationError(_(u'There is already a root node on %s') % self.site) - super(URLPath, self).clean(*args, **kwargs) + raise ValidationError(_('There is already a root node on %s') % self.site) + super().clean(*args, **kwargs) @classmethod def get_by_path(cls, path, select_related=False): @@ -262,10 +259,10 @@ def on_article_delete(instance, *args, **kwargs): other_read = False, other_write = False) article.add_revision(ArticleRevision( - content=_(u'Articles who lost their parents\n' + content=_('Articles who lost their parents\n' '===============================\n\n' 'The children of this article have had their parents deleted. You should probably find a new home for them.'), - title=_(u"Lost and found"))) + title=_("Lost and found"))) lost_and_found = URLPath.objects.create(slug=settings.LOST_AND_FOUND_SLUG, parent=URLPath.root(), site=site, diff --git a/wiki/plugins/attachments/__init__.py b/wiki/plugins/attachments/__init__.py index 44433dce7..fe5b55e46 100644 --- a/wiki/plugins/attachments/__init__.py +++ b/wiki/plugins/attachments/__init__.py @@ -1,3 +1 @@ -from __future__ import unicode_literals - default_app_config = 'wiki.apps.AttachmentsConfig' diff --git a/wiki/plugins/attachments/admin.py b/wiki/plugins/attachments/admin.py index 0f6ccdf50..c02aae1aa 100644 --- a/wiki/plugins/attachments/admin.py +++ b/wiki/plugins/attachments/admin.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from django.contrib import admin from wiki.plugins.attachments import models diff --git a/wiki/plugins/attachments/forms.py b/wiki/plugins/attachments/forms.py index 8163afe67..2b5db386f 100644 --- a/wiki/plugins/attachments/forms.py +++ b/wiki/plugins/attachments/forms.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - from django import forms from django.utils.translation import ugettext_lazy as _ @@ -9,8 +6,8 @@ class AttachmentForm(forms.ModelForm): - description = forms.CharField(label=_(u'Description'), - help_text=_(u'A short summary of what the file contains'), + description = forms.CharField(label=_('Description'), + help_text=_('A short summary of what the file contains'), required=False) class Meta: @@ -19,12 +16,12 @@ class Meta: class DeleteForm(forms.Form): """This form is both used for dereferencing and deleting attachments""" - confirm = forms.BooleanField(label=_(u'Yes I am sure...'), + confirm = forms.BooleanField(label=_('Yes I am sure...'), required=False) def clean_confirm(self): if not self.cleaned_data['confirm']: - raise forms.ValidationError(_(u'You are not sure enough!')) + raise forms.ValidationError(_('You are not sure enough!')) return True class SearchForm(forms.Form): diff --git a/wiki/plugins/attachments/markdown_extensions.py b/wiki/plugins/attachments/markdown_extensions.py index 3ccabeaf3..3767da5d8 100644 --- a/wiki/plugins/attachments/markdown_extensions.py +++ b/wiki/plugins/attachments/markdown_extensions.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - import re import markdown @@ -32,9 +30,9 @@ def run(self, lines): id=attachment_id, current_revision__deleted=False) url = reverse('wiki:attachments_download', kwargs={'article_id': self.markdown.article.id, 'attachment_id':attachment.id,}) - line = line.replace(m.group(1), u"""%s""" % - (url, _(u"Click to download file"), attachment.original_filename)) + line = line.replace(m.group(1), """%s""" % + (url, _("Click to download file"), attachment.original_filename)) except models.Attachment.DoesNotExist: - line = line.replace(m.group(1), u"""Attachment with ID #%s is deleted.""" % attachment_id) + line = line.replace(m.group(1), """Attachment with ID #%s is deleted.""" % attachment_id) new_text.append(line) return new_text diff --git a/wiki/plugins/attachments/migrations/0001_initial.py b/wiki/plugins/attachments/migrations/0001_initial.py index a3d777159..aebbc234b 100644 --- a/wiki/plugins/attachments/migrations/0001_initial.py +++ b/wiki/plugins/attachments/migrations/0001_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - from django.conf import settings from django.db import migrations, models diff --git a/wiki/plugins/attachments/models.py b/wiki/plugins/attachments/models.py index 832cc7868..74dc5862c 100644 --- a/wiki/plugins/attachments/models.py +++ b/wiki/plugins/attachments/models.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from django.db import models from django.utils.translation import ugettext_lazy as _ @@ -19,13 +17,13 @@ class Attachment(ReusablePlugin): objects = managers.ArticleFkManager() current_revision = models.OneToOneField('AttachmentRevision', - verbose_name=_(u'current revision'), + verbose_name=_('current revision'), blank=True, null=True, related_name='current_set', - help_text=_(u'The revision of this attachment currently in use (on all articles using the attachment)'), + help_text=_('The revision of this attachment currently in use (on all articles using the attachment)'), on_delete=models.CASCADE ) - original_filename = models.CharField(max_length=256, verbose_name=_(u'original filename'), blank=True, null=True) + original_filename = models.CharField(max_length=256, verbose_name=_('original filename'), blank=True, null=True) def can_write(self, **kwargs): user = kwargs.get('user', None) @@ -37,8 +35,8 @@ def can_delete(self, user): return self.can_write(user=user) class Meta: - verbose_name = _(u'attachment') - verbose_name_plural = _(u'attachments') + verbose_name = _('attachment') + verbose_name_plural = _('attachments') db_table = 'wiki_attachments_attachment' def __unicode__(self): @@ -80,14 +78,14 @@ class AttachmentRevision(BaseRevisionMixin, models.Model): attachment = models.ForeignKey('Attachment', on_delete=models.CASCADE) file = models.FileField(upload_to=upload_path, #@ReservedAssignment - verbose_name=_(u'file'), + verbose_name=_('file'), storage=settings.STORAGE_BACKEND) description = models.TextField(blank=True) class Meta: - verbose_name = _(u'attachment revision') - verbose_name_plural = _(u'attachment revisions') + verbose_name = _('attachment revision') + verbose_name_plural = _('attachment revisions') ordering = ('created',) get_latest_by = 'revision_number' db_table = 'wiki_attachments_attachmentrevision' @@ -128,7 +126,7 @@ def save(self, *args, **kwargs): except (AttachmentRevision.DoesNotExist, Attachment.DoesNotExist): self.revision_number = 1 - super(AttachmentRevision, self).save(*args, **kwargs) + super().save(*args, **kwargs) if not self.attachment.current_revision: # If I'm saved from Django admin, then article.current_revision is me! diff --git a/wiki/plugins/attachments/settings.py b/wiki/plugins/attachments/settings.py index e0fa297ab..d72149386 100644 --- a/wiki/plugins/attachments/settings.py +++ b/wiki/plugins/attachments/settings.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from django.conf import settings as django_settings from django.core.files.storage import default_storage diff --git a/wiki/plugins/attachments/views.py b/wiki/plugins/attachments/views.py index cc7896d0a..da151101e 100644 --- a/wiki/plugins/attachments/views.py +++ b/wiki/plugins/attachments/views.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - from django.contrib import messages from django.db import transaction from django.db.models import Q @@ -31,7 +28,7 @@ def dispatch(self, request, article, *args, **kwargs): self.attachments = models.Attachment.objects.active().filter(articles=article) # Fixing some weird transaction issue caused by adding commit_manually to form_valid - return super(AttachmentView, self).dispatch(request, article, *args, **kwargs) + return super().dispatch(request, article, *args, **kwargs) def form_valid(self, form): if (self.request.user.is_anonymous and not settings.ANONYMOUS or @@ -48,11 +45,11 @@ def form_valid(self, form): attachment_revision.attachment = attachment attachment_revision.set_from_request(self.request) attachment_revision.save() - messages.success(self.request, _(u'%s was successfully added.') % attachment_revision.get_filename()) + messages.success(self.request, _('%s was successfully added.') % attachment_revision.get_filename()) except models.IllegalFileExtension as e: - messages.error(self.request, _(u'Your file could not be saved: %s') % e) + messages.error(self.request, _('Your file could not be saved: %s') % e) except Exception: - messages.error(self.request, _(u'Your file could not be saved, probably because of a permission error on the web server.')) + messages.error(self.request, _('Your file could not be saved, probably because of a permission error on the web server.')) return redirect("wiki:attachments_index", path=self.urlpath.path, article_id=self.article.id) @@ -66,7 +63,7 @@ def get_context_data(self, **kwargs): kwargs['search_form'] = forms.SearchForm() kwargs['selected_tab'] = 'attachments' kwargs['anonymous_disallowed'] = self.request.user.is_anonymous and not settings.ANONYMOUS - return super(AttachmentView, self).get_context_data(**kwargs) + return super().get_context_data(**kwargs) class AttachmentHistoryView(ArticleMixin, TemplateView): @@ -79,13 +76,13 @@ def dispatch(self, request, article, attachment_id, *args, **kwargs): self.attachment = get_object_or_404(models.Attachment, id=attachment_id, articles=article) else: self.attachment = get_object_or_404(models.Attachment.objects.active(), id=attachment_id, articles=article) - return super(AttachmentHistoryView, self).dispatch(request, article, *args, **kwargs) + return super().dispatch(request, article, *args, **kwargs) def get_context_data(self, **kwargs): kwargs['attachment'] = self.attachment kwargs['revisions'] = self.attachment.attachmentrevision_set.all().order_by('-revision_number') kwargs['selected_tab'] = 'attachments' - return super(AttachmentHistoryView, self).get_context_data(**kwargs) + return super().get_context_data(**kwargs) class AttachmentReplaceView(ArticleMixin, FormView): @@ -101,7 +98,7 @@ def dispatch(self, request, article, attachment_id, *args, **kwargs): self.attachment = get_object_or_404(models.Attachment, id=attachment_id, articles=article) else: self.attachment = get_object_or_404(models.Attachment.objects.active(), id=attachment_id, articles=article) - return super(AttachmentReplaceView, self).dispatch(request, article, *args, **kwargs) + return super().dispatch(request, article, *args, **kwargs) def form_valid(self, form): @@ -113,13 +110,13 @@ def form_valid(self, form): attachment_revision.save() self.attachment.current_revision = attachment_revision self.attachment.save() - messages.success(self.request, _(u'%s uploaded and replaces old attachment.') % attachment_revision.get_filename()) + messages.success(self.request, _('%s uploaded and replaces old attachment.') % attachment_revision.get_filename()) except models.IllegalFileExtension as e: - messages.error(self.request, _(u'Your file could not be saved: %s') % e) + messages.error(self.request, _('Your file could not be saved: %s') % e) return redirect("wiki:attachments_replace", attachment_id=self.attachment.id, path=self.urlpath.path, article_id=self.article.id) except Exception: - messages.error(self.request, _(u'Your file could not be saved, probably because of a permission error on the web server.')) + messages.error(self.request, _('Your file could not be saved, probably because of a permission error on the web server.')) return redirect("wiki:attachments_replace", attachment_id=self.attachment.id, path=self.urlpath.path, article_id=self.article.id) @@ -127,7 +124,7 @@ def form_valid(self, form): def get_form(self, form_class): form = FormView.get_form(self, form_class) - form.fields['file'].help_text = _(u'Your new file will automatically be renamed to match the file already present. Files with different extensions are not allowed.') + form.fields['file'].help_text = _('Your new file will automatically be renamed to match the file already present. Files with different extensions are not allowed.') return form def get_initial(self, **kwargs): @@ -138,7 +135,7 @@ def get_context_data(self, **kwargs): kwargs['form'] = self.get_form() kwargs['attachment'] = self.attachment kwargs['selected_tab'] = 'attachments' - return super(AttachmentReplaceView, self).get_context_data(**kwargs) + return super().get_context_data(**kwargs) class AttachmentDownloadView(ArticleMixin, View): @@ -153,7 +150,7 @@ def dispatch(self, request, article, attachment_id, *args, **kwargs): self.revision = get_object_or_404(models.AttachmentRevision, id=revision_id, attachment__articles=article) else: self.revision = self.attachment.current_revision - return super(AttachmentDownloadView, self).dispatch(request, article, *args, **kwargs) + return super().dispatch(request, article, *args, **kwargs) def get(self, request, *args, **kwargs): if self.revision: @@ -176,12 +173,12 @@ def dispatch(self, request, article, attachment_id, revision_id, *args, **kwargs else: self.attachment = get_object_or_404(models.Attachment.objects.active(), id=attachment_id, articles=article) self.revision = get_object_or_404(models.AttachmentRevision, id=revision_id, attachment__articles=article) - return super(AttachmentChangeRevisionView, self).dispatch(request, article, *args, **kwargs) + return super().dispatch(request, article, *args, **kwargs) def post(self, request, *args, **kwargs): self.attachment.current_revision = self.revision self.attachment.save() - messages.success(self.request, _(u'Current revision changed for %s.') % self.attachment.original_filename) + messages.success(self.request, _('Current revision changed for %s.') % self.attachment.original_filename) return redirect("wiki:attachments_index", path=self.urlpath.path, article_id=self.article.id) @@ -196,13 +193,13 @@ class AttachmentAddView(ArticleMixin, View): @method_decorator(get_article(can_write=True)) def dispatch(self, request, article, attachment_id, *args, **kwargs): self.attachment = get_object_or_404(models.Attachment.objects.active().can_write(request.user), id=attachment_id) - return super(AttachmentAddView, self).dispatch(request, article, *args, **kwargs) + return super().dispatch(request, article, *args, **kwargs) def post(self, request, *args, **kwargs): if self.attachment.articles.filter(id=self.article.id): self.attachment.articles.add(self.article) self.attachment.save() - messages.success(self.request, _(u'Added a reference to "%(att)s" from "%(art)s".') % + messages.success(self.request, _('Added a reference to "%(att)s" from "%(art)s".') % {'att': self.attachment.original_filename, 'art': self.article.current_revision.title}) return redirect("wiki:attachments_index", path=self.urlpath.path, article_id=self.article.id) @@ -218,7 +215,7 @@ def dispatch(self, request, article, attachment_id, *args, **kwargs): self.attachment = get_object_or_404(models.Attachment, id=attachment_id, articles=article) if not self.attachment.can_delete(request.user): return response_forbidden(request, article, kwargs.get('urlpath', None)) - return super(AttachmentDeleteView, self).dispatch(request, article, *args, **kwargs) + return super().dispatch(request, article, *args, **kwargs) def form_valid(self, form): @@ -232,10 +229,10 @@ def form_valid(self, form): revision.save() self.attachment.current_revision = revision self.attachment.save() - messages.info(self.request, _(u'The file %s was deleted.') % self.attachment.original_filename) + messages.info(self.request, _('The file %s was deleted.') % self.attachment.original_filename) else: self.attachment.articles.remove(self.article) - messages.info(self.request, _(u'This article is no longer related to the file %s.') % self.attachment.original_filename) + messages.info(self.request, _('This article is no longer related to the file %s.') % self.attachment.original_filename) return redirect("wiki:get", path=self.urlpath.path, article_id=self.article.id) @@ -244,7 +241,7 @@ def get_context_data(self, **kwargs): kwargs['selected_tab'] = 'attachments' if 'form' not in kwargs: kwargs['form'] = self.get_form() - return super(AttachmentDeleteView, self).get_context_data(**kwargs) + return super().get_context_data(**kwargs) class AttachmentSearchView(ArticleMixin, ListView): @@ -256,7 +253,7 @@ class AttachmentSearchView(ArticleMixin, ListView): @method_decorator(get_article(can_write=True)) def dispatch(self, request, article, *args, **kwargs): - return super(AttachmentSearchView, self).dispatch(request, article, *args, **kwargs) + return super().dispatch(request, article, *args, **kwargs) def get_queryset(self): self.query = self.request.GET.get('query', None) diff --git a/wiki/plugins/attachments/wiki_plugin.py b/wiki/plugins/attachments/wiki_plugin.py index 4a3900590..06a4187f9 100644 --- a/wiki/plugins/attachments/wiki_plugin.py +++ b/wiki/plugins/attachments/wiki_plugin.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - from django.conf.urls import url from django.utils.translation import ugettext_lazy as _ @@ -28,13 +25,13 @@ class AttachmentPlugin(BasePlugin): url(r'^change/(?P\d+)/revision/(?P\d+)/$', views.AttachmentChangeRevisionView.as_view(), name='attachments_revision_change'), ] - article_tab = (_(u'Attachments'), "icon-file") + article_tab = (_('Attachments'), "icon-file") article_view = views.AttachmentView().dispatch # List of notifications to construct signal handlers for. This # is handled inside the notifications plugin. notifications = [{'model': models.AttachmentRevision, - 'message': lambda obj: (_(u"A file was changed: %s") if not obj.deleted else _(u"A file was deleted: %s")) % obj.get_filename(), + 'message': lambda obj: (_("A file was changed: %s") if not obj.deleted else _("A file was deleted: %s")) % obj.get_filename(), 'key': ARTICLE_EDIT, 'created': True, 'get_article': lambda obj: obj.attachment.article} diff --git a/wiki/plugins/help/models.py b/wiki/plugins/help/models.py index dbe627f0e..71a836239 100644 --- a/wiki/plugins/help/models.py +++ b/wiki/plugins/help/models.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from django.db import models # Create your models here. diff --git a/wiki/plugins/help/tests.py b/wiki/plugins/help/tests.py index 1ddecbc38..06eeec547 100644 --- a/wiki/plugins/help/tests.py +++ b/wiki/plugins/help/tests.py @@ -5,7 +5,6 @@ Replace this with more appropriate tests for your application. """ -from __future__ import absolute_import from django.test import TestCase diff --git a/wiki/plugins/help/wiki_plugin.py b/wiki/plugins/help/wiki_plugin.py index b56c79950..5ac4edb5e 100644 --- a/wiki/plugins/help/wiki_plugin.py +++ b/wiki/plugins/help/wiki_plugin.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - from django.utils.translation import ugettext_lazy as _ from wiki.core.plugins import registry diff --git a/wiki/plugins/images/__init__.py b/wiki/plugins/images/__init__.py index 5486d78b8..f2ecac4de 100644 --- a/wiki/plugins/images/__init__.py +++ b/wiki/plugins/images/__init__.py @@ -1,2 +1 @@ -from __future__ import unicode_literals default_app_config = 'wiki.apps.ImagesConfig' diff --git a/wiki/plugins/images/admin.py b/wiki/plugins/images/admin.py index ed6d5a176..1456e4112 100644 --- a/wiki/plugins/images/admin.py +++ b/wiki/plugins/images/admin.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import from django.contrib import admin from django import forms from wiki.plugins.images import models @@ -11,7 +10,7 @@ class Meta: fields = '__all__' def __init__(self, *args, **kwargs): - super(ImageForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if self.instance.pk: revisions = models.ImageRevision.objects.filter(plugin=self.instance) self.fields['current_revision'].queryset = revisions diff --git a/wiki/plugins/images/forms.py b/wiki/plugins/images/forms.py index d10b9c363..b85edbb8c 100644 --- a/wiki/plugins/images/forms.py +++ b/wiki/plugins/images/forms.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import from django import forms from django.utils.translation import ugettext_lazy as _ @@ -11,21 +10,21 @@ class SidebarForm(forms.ModelForm, PluginSidebarFormMixin): def __init__(self, article, request, *args, **kwargs): self.article = article self.request = request - super(SidebarForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def get_usermessage(self): - return _(u"New image %s was successfully uploaded. You can use it by selecting it from the list of available images.") % self.instance.get_filename() + return _("New image %s was successfully uploaded. You can use it by selecting it from the list of available images.") % self.instance.get_filename() def save(self, *args, **kwargs): if not self.instance.id: image = models.Image() image.article = self.article kwargs['commit'] = False - revision = super(SidebarForm, self).save(*args, **kwargs) + revision = super().save(*args, **kwargs) revision.set_from_request(self.request) image.add_revision(self.instance, save=True) return revision - return super(SidebarForm, self).save(*args, **kwargs) + return super().save(*args, **kwargs) class Meta: model = models.ImageRevision @@ -37,12 +36,12 @@ class RevisionForm(forms.ModelForm): def __init__(self, *args, **kwargs): self.image = kwargs.pop('image') self.request = kwargs.pop('request') - super(RevisionForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def save(self, *args, **kwargs): if not self.instance.id: kwargs['commit'] = False - revision = super(RevisionForm, self).save(*args, **kwargs) + revision = super().save(*args, **kwargs) revision.inherit_predecessor(self.image, skip_image_file=True) revision.set_from_request(self.request) #revision.save() @@ -57,10 +56,10 @@ class Meta: class PurgeForm(forms.Form): - confirm = forms.BooleanField(label=_(u'Are you sure?'), required=False) + confirm = forms.BooleanField(label=_('Are you sure?'), required=False) def clean_confirm(self): confirm = self.cleaned_data['confirm'] if not confirm: - raise forms.ValidationError(_(u'You are not sure enough!')) + raise forms.ValidationError(_('You are not sure enough!')) return confirm diff --git a/wiki/plugins/images/markdown_extensions.py b/wiki/plugins/images/markdown_extensions.py index 49d310909..8cca18c18 100644 --- a/wiki/plugins/images/markdown_extensions.py +++ b/wiki/plugins/images/markdown_extensions.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import import markdown import re diff --git a/wiki/plugins/images/migrations/0001_initial.py b/wiki/plugins/images/migrations/0001_initial.py index 3d948851b..dd2e2ddeb 100644 --- a/wiki/plugins/images/migrations/0001_initial.py +++ b/wiki/plugins/images/migrations/0001_initial.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import from django.db import models, migrations import wiki.plugins.images.models diff --git a/wiki/plugins/images/models.py b/wiki/plugins/images/models.py index 3eefbab45..cbd1df356 100644 --- a/wiki/plugins/images/models.py +++ b/wiki/plugins/images/models.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import from django.conf import settings as django_settings from django.core.exceptions import ImproperlyConfigured from django.db import models @@ -41,12 +40,12 @@ def can_delete(self, user): return self.can_write(user=user) class Meta: - verbose_name = _(u'image') - verbose_name_plural = _(u'images') + verbose_name = _('image') + verbose_name_plural = _('images') db_table = 'wiki_images_image' def __unicode__(self): - title = (_(u'Image: %s') % self.current_revision.imagerevision.get_filename()) if self.current_revision else _(u'Current revision not set!!') + title = (_('Image: %s') % self.current_revision.imagerevision.get_filename()) if self.current_revision else _('Current revision not set!!') return title @@ -90,14 +89,14 @@ def inherit_predecessor(self, image, skip_image_file=False): self.image = predecessor.image self.width = predecessor.width self.height = predecessor.height - except IOError: + except OSError: self.image = None class Meta: - verbose_name = _(u'image revision') - verbose_name_plural = _(u'image revisions') + verbose_name = _('image revision') + verbose_name_plural = _('image revisions') db_table = 'wiki_images_imagerevision' ordering = ('-created',) def __unicode__(self): - return _(u'Image Revsion: %d') % self.revision_number + return _('Image Revsion: %d') % self.revision_number diff --git a/wiki/plugins/images/settings.py b/wiki/plugins/images/settings.py index 9dac168ee..7bcc27e7b 100644 --- a/wiki/plugins/images/settings.py +++ b/wiki/plugins/images/settings.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import from django.conf import settings as django_settings # Where to store images diff --git a/wiki/plugins/images/templatetags/wiki_images_tags.py b/wiki/plugins/images/templatetags/wiki_images_tags.py index 74bbbe1e7..dcbfec8cf 100644 --- a/wiki/plugins/images/templatetags/wiki_images_tags.py +++ b/wiki/plugins/images/templatetags/wiki_images_tags.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import from django import template from wiki.plugins.images import models diff --git a/wiki/plugins/images/views.py b/wiki/plugins/images/views.py index 784994a3b..dd302369e 100644 --- a/wiki/plugins/images/views.py +++ b/wiki/plugins/images/views.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import from django.contrib import messages from django.urls import reverse from django.shortcuts import get_object_or_404, redirect @@ -24,7 +23,7 @@ class ImageView(ArticleMixin, ListView): @method_decorator(get_article(can_read=True, not_locked=True)) def dispatch(self, request, article, *args, **kwargs): - return super(ImageView, self).dispatch(request, article, *args, **kwargs) + return super().dispatch(request, article, *args, **kwargs) def get_queryset(self): if (self.article.can_moderate(self.request.user) or @@ -140,7 +139,7 @@ def dispatch(self, request, article, *args, **kwargs): return ArticleMixin.dispatch(self, request, article, *args, **kwargs) def get_form_kwargs(self, **kwargs): - kwargs = super(RevisionAddView, self).get_form_kwargs(**kwargs) + kwargs = super().get_form_kwargs(**kwargs) kwargs['image'] = self.image kwargs['request'] = self.request return kwargs @@ -150,7 +149,7 @@ def get_context_data(self, **kwargs): # with the form instance if 'form' not in kwargs: kwargs['form'] = self.get_form() - kwargs = super(RevisionAddView, self).get_context_data(**kwargs) + kwargs = super().get_context_data(**kwargs) kwargs['image'] = self.image return kwargs diff --git a/wiki/plugins/images/wiki_plugin.py b/wiki/plugins/images/wiki_plugin.py index 4eeaa0613..3873c364a 100644 --- a/wiki/plugins/images/wiki_plugin.py +++ b/wiki/plugins/images/wiki_plugin.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import from django.conf.urls import url from django.utils.translation import ugettext_lazy as _ @@ -24,7 +22,7 @@ class ImagePlugin(BasePlugin): # is handled inside the notifications plugin. notifications = [ {'model': models.ImageRevision, - 'message': lambda obj: _(u"An image was added: %s") % obj.get_filename(), + 'message': lambda obj: _("An image was added: %s") % obj.get_filename(), 'key': ARTICLE_EDIT, 'created': False, 'ignore': lambda revision: bool(revision.previous_revision), # Ignore if there is a previous revision... the image isn't new @@ -43,11 +41,11 @@ class RenderMedia: urlpatterns = [ url('^$', views.ImageView.as_view(), name='images_index'), - url('^delete/(?P\d+)/$', views.DeleteView.as_view(), name='images_delete'), - url('^restore/(?P\d+)/$', views.DeleteView.as_view(), name='images_restore', kwargs={'restore': True}), - url('^purge/(?P\d+)/$', views.PurgeView.as_view(), name='images_purge'), - url('^(?P\d+)/revision/change/(?P\d+)/$', views.RevisionChangeView.as_view(), name='images_restore'), - url('^(?P\d+)/revision/add/$', views.RevisionAddView.as_view(), name='images_add_revision'), + url(r'^delete/(?P\d+)/$', views.DeleteView.as_view(), name='images_delete'), + url(r'^restore/(?P\d+)/$', views.DeleteView.as_view(), name='images_restore', kwargs={'restore': True}), + url(r'^purge/(?P\d+)/$', views.PurgeView.as_view(), name='images_purge'), + url(r'^(?P\d+)/revision/change/(?P\d+)/$', views.RevisionChangeView.as_view(), name='images_restore'), + url(r'^(?P\d+)/revision/add/$', views.RevisionAddView.as_view(), name='images_add_revision'), ] markdown_extensions = [ImageExtension()] diff --git a/wiki/plugins/links/mdx/djangowikilinks.py b/wiki/plugins/links/mdx/djangowikilinks.py index 9316d8e4f..2de610782 100755 --- a/wiki/plugins/links/mdx/djangowikilinks.py +++ b/wiki/plugins/links/mdx/djangowikilinks.py @@ -20,7 +20,6 @@ ''' -from __future__ import absolute_import import markdown from os import path as os_path @@ -43,7 +42,7 @@ def __init__(self, **kwargs): } # Override defaults with user settings - super(WikiPathExtension, self).__init__(**kwargs) + super().__init__(**kwargs) def extendMarkdown(self, md, md_globals): self.md = md diff --git a/wiki/plugins/links/mdx/urlize.py b/wiki/plugins/links/mdx/urlize.py index a800f283b..5b43c84e2 100644 --- a/wiki/plugins/links/mdx/urlize.py +++ b/wiki/plugins/links/mdx/urlize.py @@ -39,7 +39,6 @@ """ -from __future__ import absolute_import import markdown import re diff --git a/wiki/plugins/links/models.py b/wiki/plugins/links/models.py index 00539216d..71a836239 100644 --- a/wiki/plugins/links/models.py +++ b/wiki/plugins/links/models.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import from django.db import models # Create your models here. diff --git a/wiki/plugins/links/tests.py b/wiki/plugins/links/tests.py index e585808ad..501deb776 100644 --- a/wiki/plugins/links/tests.py +++ b/wiki/plugins/links/tests.py @@ -5,7 +5,6 @@ Replace this with more appropriate tests for your application. """ -from __future__ import absolute_import from django.test import TestCase diff --git a/wiki/plugins/links/views.py b/wiki/plugins/links/views.py index c955db7d4..f17578d84 100644 --- a/wiki/plugins/links/views.py +++ b/wiki/plugins/links/views.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import from wiki.decorators import json_view, get_article from django.views.generic.base import View from django.utils.decorators import method_decorator diff --git a/wiki/plugins/links/wiki_plugin.py b/wiki/plugins/links/wiki_plugin.py index 3b72db0d2..a3fe719d7 100644 --- a/wiki/plugins/links/wiki_plugin.py +++ b/wiki/plugins/links/wiki_plugin.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import from django.conf.urls import url from django.utils.translation import ugettext_lazy as _ diff --git a/wiki/plugins/notifications/__init__.py b/wiki/plugins/notifications/__init__.py index 17d97fad5..94436a565 100644 --- a/wiki/plugins/notifications/__init__.py +++ b/wiki/plugins/notifications/__init__.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals default_app_config = 'wiki.apps.NotifcationsConfig' # Key for django_notify diff --git a/wiki/plugins/notifications/forms.py b/wiki/plugins/notifications/forms.py index b4121185f..d457aa2b7 100644 --- a/wiki/plugins/notifications/forms.py +++ b/wiki/plugins/notifications/forms.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import from django import forms from django.utils.translation import ugettext_lazy as _ @@ -12,12 +11,12 @@ class SubscriptionForm(PluginSettingsFormMixin, forms.Form): - settings_form_headline = _(u'Notifications') + settings_form_headline = _('Notifications') settings_order = 1 settings_write_access = False - edit = forms.BooleanField(required=False, label=_(u'When this article is edited')) - edit_email = forms.BooleanField(required=False, label=_(u'Also receive emails about article edits'), + edit = forms.BooleanField(required=False, label=_('When this article is edited')) + edit_email = forms.BooleanField(required=False, label=_('Also receive emails about article edits'), widget=forms.CheckboxInput(attrs={'onclick': mark_safe("$('#id_edit').attr('checked', $(this).is(':checked'));")})) def __init__(self, article, request, *args, **kwargs): @@ -36,7 +35,7 @@ def __init__(self, article, request, *args, **kwargs): initial = {'edit': bool(self.edit_notifications), 'edit_email': bool(self.edit_notifications.filter(send_emails=True))} kwargs['initial'] = initial - super(SubscriptionForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def get_usermessage(self): if self.changed_data: diff --git a/wiki/plugins/notifications/migrations/0001_initial.py b/wiki/plugins/notifications/migrations/0001_initial.py index 9f541c809..70b364467 100644 --- a/wiki/plugins/notifications/migrations/0001_initial.py +++ b/wiki/plugins/notifications/migrations/0001_initial.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from __future__ import absolute_import from django.db import models, migrations diff --git a/wiki/plugins/notifications/models.py b/wiki/plugins/notifications/models.py index a9bdca2af..9988f3523 100644 --- a/wiki/plugins/notifications/models.py +++ b/wiki/plugins/notifications/models.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import from django.urls import reverse from django.utils.translation import ugettext_lazy as _ from django.db.models import signals @@ -16,7 +14,7 @@ class ArticleSubscription(ArticlePlugin, Subscription): def __unicode__(self): - return (_(u"%(user)s subscribing to %(article)s (%(type)s)") % + return (_("%(user)s subscribing to %(article)s (%(type)s)") % {'user': self.settings.user.username, 'article': self.article.current_revision.title, 'type': self.notification_type.label}) @@ -39,13 +37,13 @@ def post_article_revision_save(instance, **kwargs): if kwargs.get('created', False): url = default_url(instance.article) if instance.deleted: - notify(_(u'Article deleted: %s') % instance.title, ARTICLE_EDIT, + notify(_('Article deleted: %s') % instance.title, ARTICLE_EDIT, target_object=instance.article, url=url) elif instance.previous_revision: - notify(_(u'Article modified: %s') % instance.title, ARTICLE_EDIT, + notify(_('Article modified: %s') % instance.title, ARTICLE_EDIT, target_object=instance.article, url=url) else: - notify(_(u'New article created: %s') % instance.title, ARTICLE_EDIT, + notify(_('New article created: %s') % instance.title, ARTICLE_EDIT, target_object=instance, url=url) diff --git a/wiki/plugins/notifications/tests.py b/wiki/plugins/notifications/tests.py index e585808ad..501deb776 100644 --- a/wiki/plugins/notifications/tests.py +++ b/wiki/plugins/notifications/tests.py @@ -5,7 +5,6 @@ Replace this with more appropriate tests for your application. """ -from __future__ import absolute_import from django.test import TestCase diff --git a/wiki/plugins/notifications/wiki_plugin.py b/wiki/plugins/notifications/wiki_plugin.py index 5f284059f..e2cf3be5d 100644 --- a/wiki/plugins/notifications/wiki_plugin.py +++ b/wiki/plugins/notifications/wiki_plugin.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import from wiki.core.plugins import registry from wiki.core.plugins.base import BasePlugin diff --git a/wiki/templatetags/wiki_tags.py b/wiki/templatetags/wiki_tags.py index 91f1cdf61..54c3a256f 100644 --- a/wiki/templatetags/wiki_tags.py +++ b/wiki/templatetags/wiki_tags.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from django import template from django.conf import settings as django_settings from django.contrib.contenttypes.models import ContentType diff --git a/wiki/urls.py b/wiki/urls.py index f0c4183b6..f89331299 100644 --- a/wiki/urls.py +++ b/wiki/urls.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - from django.conf.urls import include, url from wiki.conf import settings diff --git a/wiki/views/accounts.py b/wiki/views/accounts.py index 126e04114..850ccc092 100644 --- a/wiki/views/accounts.py +++ b/wiki/views/accounts.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- """This is nothing but the usual handling of django user accounts, so go ahead and replace it or disable it!""" -from __future__ import absolute_import from django.conf import settings as django_settings from django.contrib import messages @@ -25,14 +23,14 @@ class Signup(CreateView): template_name = "wiki/accounts/signup.html" def get_success_url(self, *args): - messages.success(self.request, _(u'You are now sign up... and now you can sign in!')) + messages.success(self.request, _('You are now sign up... and now you can sign in!')) return reverse("wiki:login") class Logout(View): def get(self, request, *args, **kwargs): auth_logout(request) - messages.info(request, _(u"You are no longer logged in. Bye bye!")) + messages.info(request, _("You are no longer logged in. Bye bye!")) return redirect("wiki:get", URLPath.root().path) class Login(FormView): @@ -42,7 +40,7 @@ class Login(FormView): def get_form_kwargs(self): self.request.session.set_test_cookie() - kwargs = super(Login, self).get_form_kwargs() + kwargs = super().get_form_kwargs() kwargs['request'] = self.request return kwargs @@ -57,7 +55,7 @@ def get(self, request, *args, **kwargs): def form_valid(self, form, *args, **kwargs): auth_login(self.request, form.get_user()) - messages.info(self.request, _(u"You are now logged in! Have fun!")) + messages.info(self.request, _("You are now logged in! Have fun!")) if self.request.GET.get("next", None): return redirect(self.request.GET['next']) if django_settings.LOGIN_REDIRECT_URL: diff --git a/wiki/views/article.py b/wiki/views/article.py index f9b3ba929..711fd4cef 100644 --- a/wiki/views/article.py +++ b/wiki/views/article.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - import difflib from django.contrib import messages @@ -14,7 +11,6 @@ from django.views.generic.base import TemplateView, View from django.views.generic.edit import FormView from django.views.generic.list import ListView -from six.moves import range from wiki import editors, forms, models from wiki.conf import settings @@ -32,7 +28,7 @@ class ArticleView(ArticleMixin, TemplateView): @method_decorator(get_article(can_read=True)) def dispatch(self, request, article, *args, **kwargs): - return super(ArticleView, self).dispatch(request, article, *args, **kwargs) + return super().dispatch(request, article, *args, **kwargs) def get_context_data(self, **kwargs): kwargs['selected_tab'] = 'view' @@ -46,7 +42,7 @@ class Create(FormView, ArticleMixin): @method_decorator(get_article(can_write=True)) def dispatch(self, request, article, *args, **kwargs): - return super(Create, self).dispatch(request, article, *args, **kwargs) + return super().dispatch(request, article, *args, **kwargs) def get_form(self, form_class=None): """ @@ -88,14 +84,14 @@ def form_valid(self, form): 'other_read': self.article.other_read, 'other_write': self.article.other_write, }) - messages.success(self.request, _(u"New article '%s' created.") % self.newpath.article.current_revision.title) + messages.success(self.request, _("New article '%s' created.") % self.newpath.article.current_revision.title) # TODO: Handle individual exceptions better and give good feedback. except Exception as e: if self.request.user.is_superuser: - messages.error(self.request, _(u"There was an error creating this article: %s") % str(e)) + messages.error(self.request, _("There was an error creating this article: %s") % str(e)) else: - messages.error(self.request, _(u"There was an error creating this article.")) + messages.error(self.request, _("There was an error creating this article.")) return redirect('wiki:get', '') url = self.get_success_url() @@ -147,13 +143,13 @@ def dispatch1(self, request, article, *args, **kwargs): else: self.cannot_delete_root = True - return super(Delete, self).dispatch(request, article, *args, **kwargs) + return super().dispatch(request, article, *args, **kwargs) def get_initial(self): return {'revision': self.article.current_revision} def get_form(self, form_class=None): - form = super(Delete, self).get_form(form_class=form_class) + form = super().get_form(form_class=form_class) if self.article.can_moderate(self.request.user): form.fields['purge'].widget = forms.forms.CheckboxInput() return form @@ -176,7 +172,7 @@ def form_valid(self, form): cannot_delete_children = True if self.cannot_delete_root or cannot_delete_children: - messages.error(self.request, _(u'This article cannot be deleted because it has children or is a root article.')) + messages.error(self.request, _('This article cannot be deleted because it has children or is a root article.')) return redirect('wiki:get', article_id=self.article.id) @@ -187,14 +183,14 @@ def form_valid(self, form): else: self.article.delete() - messages.success(self.request, _(u'This article together with all its contents are now completely gone! Thanks!')) + messages.success(self.request, _('This article together with all its contents are now completely gone! Thanks!')) else: revision = models.ArticleRevision() revision.inherit_predecessor(self.article) revision.set_from_request(self.request) revision.deleted = True self.article.add_revision(revision) - messages.success(self.request, _(u'The article "%s" is now marked as deleted! Thanks for keeping the site free from unwanted material!') % revision.title) + messages.success(self.request, _('The article "%s" is now marked as deleted! Thanks for keeping the site free from unwanted material!') % revision.title) return self.get_success_url() def get_success_url(self): @@ -214,7 +210,7 @@ def get_context_data(self, **kwargs): kwargs['delete_children'] = self.children_slice[:20] kwargs['delete_children_more'] = len(self.children_slice) > 20 kwargs['cannot_delete_children'] = cannot_delete_children - return super(Delete, self).get_context_data(**kwargs) + return super().get_context_data(**kwargs) class Edit(ArticleMixin, FormView): @@ -227,7 +223,7 @@ class Edit(ArticleMixin, FormView): def dispatch(self, request, article, *args, **kwargs): self.sidebar_plugins = plugin_registry.get_sidebar() self.sidebar = [] - return super(Edit, self).dispatch(request, article, *args, **kwargs) + return super().dispatch(request, article, *args, **kwargs) def get_form(self, form_class=None): """ @@ -262,7 +258,7 @@ def get(self, request, *args, **kwargs): else: form = None self.sidebar.append((plugin, form)) - return super(Edit, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): # Generate sidebar forms @@ -277,7 +273,7 @@ def post(self, request, *args, **kwargs): if usermessage: messages.success(self.request, usermessage) else: - messages.success(self.request, _(u'Your changes were saved.')) + messages.success(self.request, _('Your changes were saved.')) if self.urlpath: return redirect('wiki:edit', path=self.urlpath.path) return redirect('wiki:edit', article_id=self.article.id) @@ -287,7 +283,7 @@ def post(self, request, *args, **kwargs): else: form = None self.sidebar.append((plugin, form)) - return super(Edit, self).post(request, *args, **kwargs) + return super().post(request, *args, **kwargs) def form_valid(self, form): """Create a new article revision when the edit form is valid @@ -300,7 +296,7 @@ def form_valid(self, form): revision.deleted = False revision.set_from_request(self.request) self.article.add_revision(revision) - messages.success(self.request, _(u'A new revision of the article was successfully added.')) + messages.success(self.request, _('A new revision of the article was successfully added.')) return self.get_success_url() def get_success_url(self): @@ -318,7 +314,7 @@ def get_context_data(self, **kwargs): kwargs['editor'] = editors.getEditor() kwargs['selected_tab'] = 'edit' kwargs['sidebar'] = self.sidebar - return super(Edit, self).get_context_data(**kwargs) + return super().get_context_data(**kwargs) class Deleted(Delete): @@ -359,13 +355,13 @@ def dispatch(self, request, article, *args, **kwargs): revision.deleted = False revision.automatic_log = _('Restoring article') self.article.add_revision(revision) - messages.success(request, _(u'The article "%s" and its children are now restored.') % revision.title) + messages.success(request, _('The article "%s" and its children are now restored.') % revision.title) if self.urlpath: return redirect('wiki:get', path=self.urlpath.path) else: return redirect('wiki:get', article_id=article.id) - return super(Deleted, self).dispatch1(request, article, *args, **kwargs) + return super().dispatch1(request, article, *args, **kwargs) def get_initial(self): return {'revision': self.article.current_revision, @@ -385,7 +381,7 @@ class Source(ArticleMixin, TemplateView): @method_decorator(get_article(can_read=True)) def dispatch(self, request, article, *args, **kwargs): - return super(Source, self).dispatch(request, article, *args, **kwargs) + return super().dispatch(request, article, *args, **kwargs) def get_context_data(self, **kwargs): kwargs['selected_tab'] = 'source' @@ -413,7 +409,7 @@ def get_context_data(self, **kwargs): @method_decorator(get_article(can_read=True)) def dispatch(self, request, article, *args, **kwargs): - return super(History, self).dispatch(request, article, *args, **kwargs) + return super().dispatch(request, article, *args, **kwargs) class Dir(ListView, ArticleMixin): @@ -431,7 +427,7 @@ def dispatch(self, request, article, *args, **kwargs): self.query = self.filter_form.cleaned_data['query'] else: self.query = None - return super(Dir, self).dispatch(request, article, *args, **kwargs) + return super().dispatch(request, article, *args, **kwargs) def get_queryset(self): children = self.urlpath.get_children().can_read(self.request.user) @@ -478,7 +474,7 @@ class Settings(ArticleMixin, TemplateView): @method_decorator(login_required) @method_decorator(get_article(can_read=True)) def dispatch(self, request, article, *args, **kwargs): - return super(Settings, self).dispatch(request, article, *args, **kwargs) + return super().dispatch(request, article, *args, **kwargs) def get_form_classes(self,): """ @@ -512,7 +508,7 @@ def post(self, *args, **kwargs): else: form = Form(self.article, self.request) self.forms.append(form) - return super(Settings, self).get(*args, **kwargs) + return super().get(*args, **kwargs) def get(self, *args, **kwargs): self.forms = [] @@ -524,7 +520,7 @@ def get(self, *args, **kwargs): for Form in self.get_form_classes(): self.forms.append(Form(new_article, self.request)) - return super(Settings, self).get(*args, **kwargs) + return super().get(*args, **kwargs) def get_success_url(self): if self.urlpath: @@ -534,7 +530,7 @@ def get_success_url(self): def get_context_data(self, **kwargs): kwargs['selected_tab'] = 'settings' kwargs['forms'] = self.forms - return super(Settings, self).get_context_data(**kwargs) + return super().get_context_data(**kwargs) # TODO: Throw in a class-based view @@ -543,7 +539,7 @@ def change_revision(request, article, revision_id=None, urlpath=None): revision = get_object_or_404(models.ArticleRevision, article=article, id=revision_id) article.current_revision = revision article.save() - messages.success(request, _(u'The article %(title)s is now set to display revision #%(revision_number)d') % { + messages.success(request, _('The article %(title)s is now set to display revision #%(revision_number)d') % { 'title': revision.title, 'revision_number': revision.revision_number, }) @@ -566,7 +562,7 @@ def dispatch(self, request, article, *args, **kwargs): self.revision = get_object_or_404(models.ArticleRevision, article=article, id=revision_id) else: self.revision = None - return super(Preview, self).dispatch(request, article, *args, **kwargs) + return super().dispatch(request, article, *args, **kwargs) def post(self, request, *args, **kwargs): edit_form = forms.EditForm(self.article.current_revision, request.POST, preview=True) @@ -574,14 +570,14 @@ def post(self, request, *args, **kwargs): self.title = edit_form.cleaned_data['title'] self.content = edit_form.cleaned_data['content'] self.preview = True - return super(Preview, self).get(request, *args, **kwargs) + return super().get(request, *args, **kwargs) def get(self, request, *args, **kwargs): if self.revision and not self.title: self.title = self.revision.title if self.revision and not self.content: self.content = self.revision.content - return super(Preview, self).get( request, *args, **kwargs) + return super().get( request, *args, **kwargs) def get_context_data(self, **kwargs): kwargs['title'] = self.title @@ -608,7 +604,7 @@ def diff(request, revision_id, other_revision_id=None): other_changes = [] if not other_revision or other_revision.title != revision.title: - other_changes.append((_(u'New title'), revision.title)) + other_changes.append((_('New title'), revision.title)) return dict(diff=list(diff), other_changes=other_changes) @@ -632,11 +628,11 @@ def merge(request, article, revision_id, urlpath=None, template_file="wiki/previ new_revision.locked = False new_revision.title=article.current_revision.title new_revision.content=content - new_revision.automatic_log = (_(u'Merge between Revision #%(r1)d and Revision #%(r2)d') % + new_revision.automatic_log = (_('Merge between Revision #%(r1)d and Revision #%(r2)d') % {'r1': revision.revision_number, 'r2': old_revision.revision_number}) article.add_revision(new_revision, save=True) - messages.success(request, _(u'A new revision was created: Merge between Revision #%(r1)d and Revision #%(r2)d') % + messages.success(request, _('A new revision was created: Merge between Revision #%(r1)d and Revision #%(r2)d') % {'r1': revision.revision_number, 'r2': old_revision.revision_number}) if urlpath: @@ -674,7 +670,7 @@ def dispatch(self, request, *args, **kwargs): # TODO: This is too dangerous... let's say there is no root.article and we end up here, # then it might cascade to delete a lot of things on an existing installation.... / benjaoming root.delete() - return super(CreateRootView, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def form_valid(self, form): models.URLPath.create_root( @@ -685,6 +681,6 @@ def form_valid(self, form): return redirect("wiki:root") def get_context_data(self, **kwargs): - data = super(CreateRootView, self).get_context_data(**kwargs) + data = super().get_context_data(**kwargs) data['editor'] = editors.getEditor() return data diff --git a/wiki/views/mixins.py b/wiki/views/mixins.py index 45cd0cfbc..90423a9b9 100644 --- a/wiki/views/mixins.py +++ b/wiki/views/mixins.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from django.views.generic.base import TemplateResponseMixin from wiki.conf import settings @@ -21,7 +19,7 @@ def dispatch(self, request, article, *args, **kwargs): articles__article__current_revision__deleted=False, user_can_read=request.user): self.children_slice.append(child) - return super(ArticleMixin, self).dispatch(request, *args, **kwargs) + return super().dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): From 942ff68e30d72430fd908562a05f5cf19fc9a2b0 Mon Sep 17 00:00:00 2001 From: usamasadiq Date: Thu, 14 Jan 2021 18:54:37 +0500 Subject: [PATCH 60/80] Upgrade markdown version and fix tests --- requirements/base.txt | 6 ++---- requirements/constraints.txt | 3 --- requirements/test.txt | 6 ++---- wiki/plugins/links/mdx/djangowikilinks.py | 26 +++++++++++------------ wiki/plugins/links/mdx/urlize.py | 12 +++++------ 5 files changed, 23 insertions(+), 30 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 6f6947ec2..085a05d0f 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -21,10 +21,8 @@ django==2.2.17 # django-classy-tags # django-mptt # django-sekizai -markdown==2.6.11 - # via - # -c requirements/constraints.txt - # -r requirements/base.in +markdown==3.3.3 + # via -r requirements/base.in packaging==20.8 # via bleach pyparsing==2.4.7 diff --git a/requirements/constraints.txt b/requirements/constraints.txt index e5853f8af..8a2cc2b0a 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -12,6 +12,3 @@ # Use latest Django LTS version Django<2.3.0 - -# Use version 2.6 to avoid markdown errors -Markdown<2.7 diff --git a/requirements/test.txt b/requirements/test.txt index 930e68721..1b2ca1cfa 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -30,10 +30,8 @@ django-sekizai==2.0.0 # django-sekizai iniconfig==1.1.1 # via pytest -markdown==2.6.11 - # via - # -c requirements/constraints.txt - # -r requirements/base.txt +markdown==3.3.3 + # via -r requirements/base.txt packaging==20.8 # via # -r requirements/base.txt diff --git a/wiki/plugins/links/mdx/djangowikilinks.py b/wiki/plugins/links/mdx/djangowikilinks.py index 2de610782..77e8a9d9a 100755 --- a/wiki/plugins/links/mdx/djangowikilinks.py +++ b/wiki/plugins/links/mdx/djangowikilinks.py @@ -40,16 +40,16 @@ def __init__(self, **kwargs): 'live_lookups': [True, 'If the plugin should try and match links to real articles'], 'default_level': [2, 'The level that most articles are created at. Relative links will tend to start at that level.'] } - + # Override defaults with user settings super().__init__(**kwargs) - + def extendMarkdown(self, md, md_globals): self.md = md - + # append to end of inline patterns WIKI_RE = r'\[(?P[^\]]+?)\]\(wiki:(?P[a-zA-Z\d\./_-]*)\)' - wikiPathPattern = WikiPath(WIKI_RE, self.config, markdown_instance=md) + wikiPathPattern = WikiPath(WIKI_RE, self.config, md=md) wikiPathPattern.md = md md.inlinePatterns.add('djangowikipath', wikiPathPattern, " 0: urlpath = lookup[0] path = urlpath.get_absolute_url() else: urlpath = None path = self.config['base_url'][0] + path_from_link - + label = m.group('linkTitle') a = etree.Element('a') a.set('href', path) @@ -116,9 +116,9 @@ def handleMatch(self, m) : else: a.set('class', self.config['html_class'][0]) a.text = label - + return a - + def _getMeta(self): """ Return meta data or config data. """ base_url = self.config['base_url'][0] diff --git a/wiki/plugins/links/mdx/urlize.py b/wiki/plugins/links/mdx/urlize.py index 5b43c84e2..68e2b591d 100644 --- a/wiki/plugins/links/mdx/urlize.py +++ b/wiki/plugins/links/mdx/urlize.py @@ -58,8 +58,8 @@ class UrlizePattern(markdown.inlinepatterns.Pattern): def __init__(self, pattern, markdown_instance=None): - markdown.inlinepatterns.Pattern.__init__(self, pattern, markdown_instance=markdown_instance) - self.compiled_re = re.compile("^(.*?)%s(.*?)$" % pattern, + markdown.inlinepatterns.Pattern.__init__(self, pattern, md=markdown_instance) + self.compiled_re = re.compile("^(.*?)%s(.*?)$" % pattern, re.DOTALL | re.UNICODE | re.IGNORECASE) """ Return a link Element given an autolink (`http://example/com`). """ @@ -68,18 +68,18 @@ def handleMatch(self, m): if url.startswith('<'): url = url[1:-1] - + text = url - + if not url.split('://')[0] in ('http','https','ftp'): if '@' in url and not '/' in url: url = 'mailto:' + url else: url = 'http://' + url - + icon = markdown.util.etree.Element("span") icon.set('class', 'icon-globe') - + span_text = markdown.util.etree.Element("span") span_text.text = markdown.util.AtomicString(" " + text) el = markdown.util.etree.Element("a") From 8559e818395a1ae9946d7cedb2a7de8ffbfa292d Mon Sep 17 00:00:00 2001 From: usamasadiq Date: Sat, 16 Jan 2021 00:04:50 +0500 Subject: [PATCH 61/80] Remove six and fix django version handling --- Makefile | 1 + requirements/base.in | 1 - requirements/base.txt | 4 +--- requirements/django.txt | 2 +- requirements/pip.in | 3 +++ requirements/pip.txt | 8 ++++++++ tox.ini | 2 +- wiki/core/plugins/registry.py | 14 ++++++-------- wiki/models/__init__.py | 1 - 9 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index 0e257852a..aac2eaff4 100644 --- a/Makefile +++ b/Makefile @@ -9,5 +9,6 @@ upgrade: pip-compile --upgrade -o requirements/tox.txt requirements/tox.in pip-compile --upgrade -o requirements/ci.txt requirements/ci.in # Let tox control the Django version for tests + grep -e "^django==" requirements/base.txt > requirements/django.txt sed '/^[dD]jango==/d' requirements/test.txt > requirements/test.tmp mv requirements/test.tmp requirements/test.txt diff --git a/requirements/base.in b/requirements/base.in index bb1d13c64..8feb9227e 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -6,5 +6,4 @@ django django-mptt django-sekizai Markdown -six # Use for strings and unicode compatibility sorl-thumbnail # Required by wiki.plugins.images diff --git a/requirements/base.txt b/requirements/base.txt index 085a05d0f..71432101a 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -30,9 +30,7 @@ pyparsing==2.4.7 pytz==2020.5 # via django six==1.15.0 - # via - # -r requirements/base.in - # bleach + # via bleach sorl-thumbnail==12.7.0 # via -r requirements/base.in sqlparse==0.4.1 diff --git a/requirements/django.txt b/requirements/django.txt index 3cdbc8d88..34249f7c4 100644 --- a/requirements/django.txt +++ b/requirements/django.txt @@ -1 +1 @@ -django==2.2.14 # via -c requirements/constraints.txt, -r requirements/base.in, django-classy-tags, django-mptt, django-sekizai +django==2.2.17 diff --git a/requirements/pip.in b/requirements/pip.in index e69de29bb..7015e2e2f 100644 --- a/requirements/pip.in +++ b/requirements/pip.in @@ -0,0 +1,3 @@ +pip +setuptools +wheel diff --git a/requirements/pip.txt b/requirements/pip.txt index 7a338ac77..9acedf7fe 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -4,3 +4,11 @@ # # make upgrade # +wheel==0.36.2 + # via -r requirements/pip.in + +# The following packages are considered to be unsafe in a requirements file: +pip==20.3.3 + # via -r requirements/pip.in +setuptools==51.1.2 + # via -r requirements/pip.in diff --git a/tox.ini b/tox.ini index bcda8d32c..f2edeb42a 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ envlist = py38-django{22,30,31} [testenv] deps = - django22: Django>=2.2,<2.3 + django22: -r requirements/django.txt django30: Django>=3.0,<3.1 django31: Django>=3.1,<3.2 -r{toxinidir}/requirements/test.txt diff --git a/wiki/core/plugins/registry.py b/wiki/core/plugins/registry.py index beae89c74..ecbb598f0 100644 --- a/wiki/core/plugins/registry.py +++ b/wiki/core/plugins/registry.py @@ -1,7 +1,5 @@ from importlib import import_module -import six - _cache = {} _settings_forms = [] _markdown_extensions = [] @@ -17,7 +15,7 @@ def register(PluginClass): raise Exception("Plugin class already registered") plugin = PluginClass() _cache[PluginClass] = plugin - + settings_form = getattr(PluginClass, 'settings_form', None) if settings_form: if isinstance(settings_form, str): @@ -26,16 +24,16 @@ def register(PluginClass): form_module = import_module(modulename) settings_form = getattr(form_module, klassname) _settings_forms.append(settings_form) - - + + if getattr(PluginClass, 'article_tab', None): _article_tabs.append(plugin) - + if getattr(PluginClass, 'sidebar', None): _sidebar.append(plugin) - _markdown_extensions.extend(getattr(PluginClass, 'markdown_extensions', [])) - + _markdown_extensions.extend(getattr(PluginClass, 'markdown_extensions', [])) + def get_plugins(): """Get loaded plugins - do not call before all plugins are loaded.""" return _cache diff --git a/wiki/models/__init__.py b/wiki/models/__init__.py index eccd2383d..e95e840fa 100644 --- a/wiki/models/__init__.py +++ b/wiki/models/__init__.py @@ -1,6 +1,5 @@ from django.conf import settings as django_settings from django.core.exceptions import ImproperlyConfigured -from six import string_types, text_type # TODO: Don't use wildcards from .article import * From bc3d08921fb9dad23682a7345c58315f3fadfb2b Mon Sep 17 00:00:00 2001 From: jinder1s Date: Tue, 19 Jan 2021 16:24:47 -0500 Subject: [PATCH 62/80] Adding Apache license to repo --- LICENSE | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From be277bdc3ef6481aa4e04a126aeef70ed3bebba6 Mon Sep 17 00:00:00 2001 From: jinder1s Date: Wed, 20 Jan 2021 07:06:35 -0500 Subject: [PATCH 63/80] Revert "Adding Apache license to repo" This reverts commit f4fe1771da5910d234b41a517bab2f4bb598701c. I accidentally pushed without review. --- LICENSE | 202 -------------------------------------------------------- 1 file changed, 202 deletions(-) delete mode 100644 LICENSE diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d64569567..000000000 --- a/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. From 1af03bdd4108342d5d6f9c1c5430c3f7c44c122b Mon Sep 17 00:00:00 2001 From: Julia Eskew Date: Fri, 30 Jul 2021 17:10:14 -0400 Subject: [PATCH 64/80] chore: Incorporate wiki improvements from upstream repository. --- setup.py | 2 +- wiki/conf/settings.py | 72 +++++++++++++++++++++++++++++++++++++++++++ wiki/core/__init__.py | 18 +++++++++-- 3 files changed, 88 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index dcba30e68..fd9477e66 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,7 @@ def is_requirement(line): setup( name="django-wiki", - version="1.0.0", + version="1.0.1", author="Benjamin Bach", author_email="benjamin@overtag.dk", description="A wiki system written for the Django framework.", diff --git a/wiki/conf/settings.py b/wiki/conf/settings.py index e4d8c8bfe..e2cdcfe78 100644 --- a/wiki/conf/settings.py +++ b/wiki/conf/settings.py @@ -1,3 +1,4 @@ +import bleach from django.conf import settings as django_settings from django.urls import reverse_lazy @@ -13,6 +14,77 @@ MARKDOWN_EXTENSIONS = getattr(django_settings, 'WIKI_MARKDOWN_EXTENSIONS', ['extra', 'toc']) +######################## +## HTML sanitization code copied from the upstream repo +######################## + +#: Whether to use Bleach or not. It's not recommended to turn this off unless +#: you know what you're doing and you don't want to use the other options. +MARKDOWN_SANITIZE_HTML = getattr(django_settings, "WIKI_MARKDOWN_SANITIZE_HTML", True) + +_default_tag_whitelists = ( + bleach.ALLOWED_TAGS + + [ + "figure", + "figcaption", + "br", + "hr", + "p", + "div", + "img", + "pre", + "span", + "sup", + "table", + "thead", + "tbody", + "th", + "tr", + "td", + "dl", + "dt", + "dd", + ] + + ["h{}".format(n) for n in range(1, 7)] +) + + +#: List of allowed tags in Markdown article contents. +MARKDOWN_HTML_WHITELIST = _default_tag_whitelists +MARKDOWN_HTML_WHITELIST += getattr(django_settings, "WIKI_MARKDOWN_HTML_WHITELIST", []) + +_default_attribute_whitelist = bleach.ALLOWED_ATTRIBUTES +for tag in MARKDOWN_HTML_WHITELIST: + if tag not in _default_attribute_whitelist: + _default_attribute_whitelist[tag] = [] + _default_attribute_whitelist[tag].append("class") + _default_attribute_whitelist[tag].append("id") + _default_attribute_whitelist[tag].append("target") + _default_attribute_whitelist[tag].append("rel") + +_default_attribute_whitelist["img"].append("src") +_default_attribute_whitelist["img"].append("alt") + +#: Dictionary of allowed attributes in Markdown article contents. +MARKDOWN_HTML_ATTRIBUTES = _default_attribute_whitelist +MARKDOWN_HTML_ATTRIBUTES.update( + getattr(django_settings, "WIKI_MARKDOWN_HTML_ATTRIBUTES", {}) +) + +#: Allowed inline styles in Markdown article contents, default is no styles +#: (empty list). +MARKDOWN_HTML_STYLES = getattr(django_settings, "WIKI_MARKDOWN_HTML_STYLES", []) + +_project_defined_attrs = getattr( + django_settings, "WIKI_MARKDOWN_HTML_ATTRIBUTE_WHITELIST", False +) + +# If styles are allowed but no custom attributes are defined, we allow styles +# for all kinds of tags. +if MARKDOWN_HTML_STYLES and not _project_defined_attrs: + MARKDOWN_HTML_ATTRIBUTES["*"] = "style" + + # This slug is used in URLPath if an article has been deleted. The children of the # URLPath of that article are moved to lost and found. They keep their permissions # and all their content. diff --git a/wiki/core/__init__.py b/wiki/core/__init__.py index 4a73ce8ac..4ddaa37db 100644 --- a/wiki/core/__init__.py +++ b/wiki/core/__init__.py @@ -1,15 +1,27 @@ import bleach import markdown +from wiki.conf import settings class ArticleMarkdown(markdown.Markdown): def __init__(self, article, *args, **kwargs): - markdown.Markdown.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) self.article = article + def convert(self, text, *args, **kwargs): + html = super().convert(text, *args, **kwargs) + if settings.MARKDOWN_SANITIZE_HTML: + html = bleach.clean( + html, + tags=settings.MARKDOWN_HTML_WHITELIST, + attributes=settings.MARKDOWN_HTML_ATTRIBUTES, + styles=settings.MARKDOWN_HTML_STYLES, + strip=True, + ) + return html def article_markdown(text, article, *args, **kwargs): md = ArticleMarkdown(article, *args, **kwargs) - text = bleach.clean(text, strip=True) - return md.convert(text) + html = md.convert(text) + return html From f9ae6acca9a60917de03d9f61d08b313be15683a Mon Sep 17 00:00:00 2001 From: Julia Eskew Date: Fri, 30 Jul 2021 17:48:30 -0400 Subject: [PATCH 65/80] Chore: Bump version to 1.0.2 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fd9477e66..1fdad0774 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,7 @@ def is_requirement(line): setup( name="django-wiki", - version="1.0.1", + version="1.0.2", author="Benjamin Bach", author_email="benjamin@overtag.dk", description="A wiki system written for the Django framework.", From cd7c6902b490a77c4ea220127e026922bcb5e005 Mon Sep 17 00:00:00 2001 From: "M. Zulqarnain" Date: Fri, 3 Sep 2021 13:07:22 +0500 Subject: [PATCH 66/80] Create upgrade-python-requirements.yml --- .../workflows/upgrade-python-requirements.yml | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 .github/workflows/upgrade-python-requirements.yml diff --git a/.github/workflows/upgrade-python-requirements.yml b/.github/workflows/upgrade-python-requirements.yml new file mode 100644 index 000000000..e4fd36ca9 --- /dev/null +++ b/.github/workflows/upgrade-python-requirements.yml @@ -0,0 +1,68 @@ +name: Upgrade Requirements + +on: + schedule: + # will start the job at 2 every Tuesday (UTC) + - cron: "0 2 * * 2" + workflow_dispatch: + inputs: + branch: + description: "Target branch to create requirements PR against" + required: true + default: 'master' + +jobs: + upgrade_requirements: + runs-on: ubuntu-20.04 + + strategy: + matrix: + python-version: ["3.8"] + + steps: + - name: setup target branch + run: echo "target_branch=$(if ['${{ github.event.inputs.branch }}' = '']; then echo 'master'; else echo '${{ github.event.inputs.branch }}'; fi)" >> $GITHUB_ENV + + - uses: actions/checkout@v1 + with: + ref: ${{ env.target_branch }} + + - name: setup python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: make upgrade + run: | + cd $GITHUB_WORKSPACE + make upgrade + + - name: setup testeng-ci + run: | + git clone https://github.com/edx/testeng-ci.git + cd $GITHUB_WORKSPACE/testeng-ci + pip install -r requirements/base.txt + - name: create pull request + env: + GITHUB_TOKEN: ${{ secrets.REQUIREMENTS_BOT_GITHUB_TOKEN }} + GITHUB_USER_EMAIL: ${{ secrets.REQUIREMENTS_BOT_GITHUB_EMAIL }} + run: | # replace user-reviewers and team-reviewers accordingly + cd $GITHUB_WORKSPACE/testeng-ci + python -m jenkins.pull_request_creator --repo-root=$GITHUB_WORKSPACE \ + --target-branch="${{ env.target_branch }}" --base-branch-name="upgrade-python-requirements" \ + --commit-message="chore: Updating Python Requirements" --pr-title="Python Requirements Update" \ + --pr-body="Python requirements update.Please review the [changelogs](https://openedx.atlassian.net/wiki/spaces/TE/pages/1001521320/Python+Package+Changelogs) for the upgraded packages." \ + --user-reviewers="" --team-reviewers="arbi-bom" --delete-old-pull-requests + + - name: Send failure notification + if: ${{ failure() }} + uses: dawidd6/action-send-mail@v3 + with: + server_address: email-smtp.us-east-1.amazonaws.com + server_port: 465 + username: ${{secrets.EDX_SMTP_USERNAME}} + password: ${{secrets.EDX_SMTP_PASSWORD}} + subject: Upgrade python requirements workflow failed in ${{github.repository}} + to: arbi-bom@edx.org # replace the email with team's email address + from: github-actions + body: Upgrade python requirements workflow in ${{github.repository}} failed! For details see "github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" From 47493ff9e2cfd083ecc64274566d426a27e8cc17 Mon Sep 17 00:00:00 2001 From: "M. Zulqarnain" Date: Fri, 3 Sep 2021 14:30:42 +0500 Subject: [PATCH 67/80] fix: branch name in upgrade job --- .github/workflows/upgrade-python-requirements.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/upgrade-python-requirements.yml b/.github/workflows/upgrade-python-requirements.yml index e4fd36ca9..1c485ab5b 100644 --- a/.github/workflows/upgrade-python-requirements.yml +++ b/.github/workflows/upgrade-python-requirements.yml @@ -9,7 +9,7 @@ on: branch: description: "Target branch to create requirements PR against" required: true - default: 'master' + default: 'edx_release' jobs: upgrade_requirements: @@ -21,7 +21,7 @@ jobs: steps: - name: setup target branch - run: echo "target_branch=$(if ['${{ github.event.inputs.branch }}' = '']; then echo 'master'; else echo '${{ github.event.inputs.branch }}'; fi)" >> $GITHUB_ENV + run: echo "target_branch=$(if ['${{ github.event.inputs.branch }}' = '']; then echo 'edx_release'; else echo '${{ github.event.inputs.branch }}'; fi)" >> $GITHUB_ENV - uses: actions/checkout@v1 with: From 0c48cc0098a208e2a126901b61aa1b7b1a8f7cc5 Mon Sep 17 00:00:00 2001 From: edX requirements bot Date: Fri, 3 Sep 2021 14:42:19 +0500 Subject: [PATCH 68/80] chore: Updating Python Requirements --- requirements/base.txt | 15 +++++++-------- requirements/ci.txt | 18 +++++++++++------- requirements/django.txt | 2 +- requirements/docs.txt | 36 ++++++++++++++++++------------------ requirements/pip.txt | 6 +++--- requirements/pip_tools.txt | 11 +++++++++-- requirements/test.txt | 29 +++++++++++++++-------------- requirements/tox.txt | 16 +++++++++------- 8 files changed, 73 insertions(+), 60 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 71432101a..488a16a4f 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -4,32 +4,31 @@ # # make upgrade # -bleach==3.2.1 +bleach==4.1.0 # via -r requirements/base.in django-classy-tags==2.0.0 # via django-sekizai django-js-asset==1.2.2 # via django-mptt -django-mptt==0.11.0 +django-mptt==0.13.2 # via -r requirements/base.in django-sekizai==2.0.0 # via -r requirements/base.in -django==2.2.17 +django==2.2.24 # via # -c requirements/constraints.txt # -r requirements/base.in # django-classy-tags - # django-mptt # django-sekizai -markdown==3.3.3 +markdown==3.3.4 # via -r requirements/base.in -packaging==20.8 +packaging==21.0 # via bleach pyparsing==2.4.7 # via packaging -pytz==2020.5 +pytz==2021.1 # via django -six==1.15.0 +six==1.16.0 # via bleach sorl-thumbnail==12.7.0 # via -r requirements/base.in diff --git a/requirements/ci.txt b/requirements/ci.txt index 72b43e872..7b892ce0b 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -4,11 +4,11 @@ # # make upgrade # -appdirs==1.4.4 +backports.entry-points-selectable==1.1.0 # via # -r requirements/tox.txt # virtualenv -distlib==0.3.1 +distlib==0.3.2 # via # -r requirements/tox.txt # virtualenv @@ -17,11 +17,15 @@ filelock==3.0.12 # -r requirements/tox.txt # tox # virtualenv -packaging==20.8 +packaging==21.0 # via # -r requirements/tox.txt # tox -pluggy==0.13.1 +platformdirs==2.3.0 + # via + # -r requirements/tox.txt + # virtualenv +pluggy==1.0.0 # via # -r requirements/tox.txt # tox @@ -33,7 +37,7 @@ pyparsing==2.4.7 # via # -r requirements/tox.txt # packaging -six==1.15.0 +six==1.16.0 # via # -r requirements/tox.txt # tox @@ -42,9 +46,9 @@ toml==0.10.2 # via # -r requirements/tox.txt # tox -tox==3.21.1 +tox==3.24.3 # via -r requirements/tox.txt -virtualenv==20.3.1 +virtualenv==20.7.2 # via # -r requirements/tox.txt # tox diff --git a/requirements/django.txt b/requirements/django.txt index 34249f7c4..f44fd3316 100644 --- a/requirements/django.txt +++ b/requirements/django.txt @@ -1 +1 @@ -django==2.2.17 +django==2.2.24 diff --git a/requirements/docs.txt b/requirements/docs.txt index cf7b7199c..fc0b9b8c1 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -6,39 +6,39 @@ # alabaster==0.7.12 # via sphinx -babel==2.9.0 +babel==2.9.1 # via sphinx -certifi==2020.12.5 +certifi==2021.5.30 # via requests -chardet==4.0.0 +charset-normalizer==2.0.4 # via requests -docutils==0.16 +docutils==0.17.1 # via sphinx -edx-sphinx-theme==1.6.0 +edx-sphinx-theme==3.0.0 # via -r requirements/docs.in -idna==2.10 +idna==3.2 # via requests imagesize==1.2.0 # via sphinx -jinja2==2.11.2 +jinja2==3.0.1 # via sphinx -markupsafe==1.1.1 +markupsafe==2.0.1 # via jinja2 -packaging==20.8 +packaging==21.0 # via sphinx -pygments==2.7.4 +pygments==2.10.0 # via sphinx pyparsing==2.4.7 # via packaging -pytz==2020.5 +pytz==2021.1 # via babel -requests==2.25.1 +requests==2.26.0 # via sphinx -six==1.15.0 +six==1.16.0 # via edx-sphinx-theme -snowballstemmer==2.0.0 +snowballstemmer==2.1.0 # via sphinx -sphinx==3.4.3 +sphinx==4.1.2 # via # -r requirements/docs.in # edx-sphinx-theme @@ -46,15 +46,15 @@ sphinxcontrib-applehelp==1.0.2 # via sphinx sphinxcontrib-devhelp==1.0.2 # via sphinx -sphinxcontrib-htmlhelp==1.0.3 +sphinxcontrib-htmlhelp==2.0.0 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx sphinxcontrib-qthelp==1.0.3 # via sphinx -sphinxcontrib-serializinghtml==1.1.4 +sphinxcontrib-serializinghtml==1.1.5 # via sphinx -urllib3==1.26.2 +urllib3==1.26.6 # via requests # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/pip.txt b/requirements/pip.txt index 9acedf7fe..8ac35d398 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -4,11 +4,11 @@ # # make upgrade # -wheel==0.36.2 +wheel==0.37.0 # via -r requirements/pip.in # The following packages are considered to be unsafe in a requirements file: -pip==20.3.3 +pip==21.2.4 # via -r requirements/pip.in -setuptools==51.1.2 +setuptools==57.4.0 # via -r requirements/pip.in diff --git a/requirements/pip_tools.txt b/requirements/pip_tools.txt index 53310ee5c..a7e1d9d2f 100644 --- a/requirements/pip_tools.txt +++ b/requirements/pip_tools.txt @@ -4,10 +4,17 @@ # # make upgrade # -click==7.1.2 +click==8.0.1 # via pip-tools -pip-tools==5.5.0 +pep517==0.11.0 + # via pip-tools +pip-tools==6.2.0 # via -r requirements/pip_tools.in +tomli==1.2.1 + # via pep517 +wheel==0.37.0 + # via pip-tools # The following packages are considered to be unsafe in a requirements file: # pip +# setuptools diff --git a/requirements/test.txt b/requirements/test.txt index 1b2ca1cfa..e279008cf 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -4,11 +4,11 @@ # # make upgrade # -attrs==20.3.0 +attrs==21.2.0 # via pytest -bleach==3.2.1 +bleach==4.1.0 # via -r requirements/base.txt -coverage==5.3.1 +coverage==5.5 # via pytest-cov django-classy-tags==2.0.0 # via @@ -18,7 +18,7 @@ django-js-asset==1.2.2 # via # -r requirements/base.txt # django-mptt -django-mptt==0.11.0 +django-mptt==0.13.2 # via -r requirements/base.txt django-sekizai==2.0.0 # via -r requirements/base.txt @@ -26,18 +26,17 @@ django-sekizai==2.0.0 # -c requirements/constraints.txt # -r requirements/base.txt # django-classy-tags - # django-mptt # django-sekizai iniconfig==1.1.1 # via pytest -markdown==3.3.3 +markdown==3.3.4 # via -r requirements/base.txt -packaging==20.8 +packaging==21.0 # via # -r requirements/base.txt # bleach # pytest -pluggy==0.13.1 +pluggy==1.0.0 # via pytest py==1.10.0 # via pytest @@ -45,20 +44,20 @@ pyparsing==2.4.7 # via # -r requirements/base.txt # packaging -pytest-cov==2.10.1 +pytest-cov==2.12.1 # via -r requirements/test.in -pytest-django==4.1.0 +pytest-django==4.4.0 # via -r requirements/test.in -pytest==6.2.1 +pytest==6.2.5 # via # -r requirements/test.in # pytest-cov # pytest-django -pytz==2020.5 +pytz==2021.1 # via # -r requirements/base.txt # django -six==1.15.0 +six==1.16.0 # via # -r requirements/base.txt # bleach @@ -69,7 +68,9 @@ sqlparse==0.4.1 # -r requirements/base.txt # django toml==0.10.2 - # via pytest + # via + # pytest + # pytest-cov webencodings==0.5.1 # via # -r requirements/base.txt diff --git a/requirements/tox.txt b/requirements/tox.txt index 4214a5e52..3b1ad6380 100644 --- a/requirements/tox.txt +++ b/requirements/tox.txt @@ -4,29 +4,31 @@ # # make upgrade # -appdirs==1.4.4 +backports.entry-points-selectable==1.1.0 # via virtualenv -distlib==0.3.1 +distlib==0.3.2 # via virtualenv filelock==3.0.12 # via # tox # virtualenv -packaging==20.8 +packaging==21.0 # via tox -pluggy==0.13.1 +platformdirs==2.3.0 + # via virtualenv +pluggy==1.0.0 # via tox py==1.10.0 # via tox pyparsing==2.4.7 # via packaging -six==1.15.0 +six==1.16.0 # via # tox # virtualenv toml==0.10.2 # via tox -tox==3.21.1 +tox==3.24.3 # via -r requirements/tox.in -virtualenv==20.3.1 +virtualenv==20.7.2 # via tox From 4662bed3d837ca4682bc1a1268dc3cbb15eaf7e5 Mon Sep 17 00:00:00 2001 From: edX requirements bot Date: Tue, 7 Sep 2021 07:11:02 +0500 Subject: [PATCH 69/80] chore: Updating Python Requirements --- requirements/base.txt | 14 +++++++------- requirements/ci.txt | 2 +- requirements/docs.txt | 2 +- requirements/pip.txt | 4 ++-- requirements/pip_tools.txt | 2 +- requirements/test.txt | 20 ++++++++++---------- requirements/tox.txt | 2 +- 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 488a16a4f..f1940258f 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,11 +1,17 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.8 # To update, run: # # make upgrade # bleach==4.1.0 # via -r requirements/base.in +django==2.2.24 + # via + # -c requirements/constraints.txt + # -r requirements/base.in + # django-classy-tags + # django-sekizai django-classy-tags==2.0.0 # via django-sekizai django-js-asset==1.2.2 @@ -14,12 +20,6 @@ django-mptt==0.13.2 # via -r requirements/base.in django-sekizai==2.0.0 # via -r requirements/base.in -django==2.2.24 - # via - # -c requirements/constraints.txt - # -r requirements/base.in - # django-classy-tags - # django-sekizai markdown==3.3.4 # via -r requirements/base.in packaging==21.0 diff --git a/requirements/ci.txt b/requirements/ci.txt index 7b892ce0b..6781b8f39 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.8 # To update, run: # # make upgrade diff --git a/requirements/docs.txt b/requirements/docs.txt index fc0b9b8c1..9ee4ad8a0 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.8 # To update, run: # # make upgrade diff --git a/requirements/pip.txt b/requirements/pip.txt index 8ac35d398..1ca06519a 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.8 # To update, run: # # make upgrade @@ -10,5 +10,5 @@ wheel==0.37.0 # The following packages are considered to be unsafe in a requirements file: pip==21.2.4 # via -r requirements/pip.in -setuptools==57.4.0 +setuptools==58.0.2 # via -r requirements/pip.in diff --git a/requirements/pip_tools.txt b/requirements/pip_tools.txt index a7e1d9d2f..ac3012fad 100644 --- a/requirements/pip_tools.txt +++ b/requirements/pip_tools.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.8 # To update, run: # # make upgrade diff --git a/requirements/test.txt b/requirements/test.txt index e279008cf..b6397ebb1 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.8 # To update, run: # # make upgrade @@ -10,6 +10,11 @@ bleach==4.1.0 # via -r requirements/base.txt coverage==5.5 # via pytest-cov + # via + # -c requirements/constraints.txt + # -r requirements/base.txt + # django-classy-tags + # django-sekizai django-classy-tags==2.0.0 # via # -r requirements/base.txt @@ -22,11 +27,6 @@ django-mptt==0.13.2 # via -r requirements/base.txt django-sekizai==2.0.0 # via -r requirements/base.txt - # via - # -c requirements/constraints.txt - # -r requirements/base.txt - # django-classy-tags - # django-sekizai iniconfig==1.1.1 # via pytest markdown==3.3.4 @@ -44,15 +44,15 @@ pyparsing==2.4.7 # via # -r requirements/base.txt # packaging -pytest-cov==2.12.1 - # via -r requirements/test.in -pytest-django==4.4.0 - # via -r requirements/test.in pytest==6.2.5 # via # -r requirements/test.in # pytest-cov # pytest-django +pytest-cov==2.12.1 + # via -r requirements/test.in +pytest-django==4.4.0 + # via -r requirements/test.in pytz==2021.1 # via # -r requirements/base.txt diff --git a/requirements/tox.txt b/requirements/tox.txt index 3b1ad6380..db0ae771f 100644 --- a/requirements/tox.txt +++ b/requirements/tox.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.8 # To update, run: # # make upgrade From a056654edbb9c9c2931b0bba9e9ec55f9b564cf6 Mon Sep 17 00:00:00 2001 From: edX requirements bot Date: Tue, 14 Sep 2021 07:12:34 +0500 Subject: [PATCH 70/80] chore: Updating Python Requirements --- requirements/base.txt | 4 ++-- requirements/docs.txt | 2 +- requirements/pip.txt | 2 +- requirements/test.txt | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index f1940258f..d1c3ba0f7 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -16,7 +16,7 @@ django-classy-tags==2.0.0 # via django-sekizai django-js-asset==1.2.2 # via django-mptt -django-mptt==0.13.2 +django-mptt==0.13.3 # via -r requirements/base.in django-sekizai==2.0.0 # via -r requirements/base.in @@ -32,7 +32,7 @@ six==1.16.0 # via bleach sorl-thumbnail==12.7.0 # via -r requirements/base.in -sqlparse==0.4.1 +sqlparse==0.4.2 # via django webencodings==0.5.1 # via bleach diff --git a/requirements/docs.txt b/requirements/docs.txt index 9ee4ad8a0..962e54116 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -38,7 +38,7 @@ six==1.16.0 # via edx-sphinx-theme snowballstemmer==2.1.0 # via sphinx -sphinx==4.1.2 +sphinx==4.2.0 # via # -r requirements/docs.in # edx-sphinx-theme diff --git a/requirements/pip.txt b/requirements/pip.txt index 1ca06519a..ca02776da 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -10,5 +10,5 @@ wheel==0.37.0 # The following packages are considered to be unsafe in a requirements file: pip==21.2.4 # via -r requirements/pip.in -setuptools==58.0.2 +setuptools==58.0.4 # via -r requirements/pip.in diff --git a/requirements/test.txt b/requirements/test.txt index b6397ebb1..600eb7494 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -23,7 +23,7 @@ django-js-asset==1.2.2 # via # -r requirements/base.txt # django-mptt -django-mptt==0.13.2 +django-mptt==0.13.3 # via -r requirements/base.txt django-sekizai==2.0.0 # via -r requirements/base.txt @@ -63,7 +63,7 @@ six==1.16.0 # bleach sorl-thumbnail==12.7.0 # via -r requirements/base.txt -sqlparse==0.4.1 +sqlparse==0.4.2 # via # -r requirements/base.txt # django From 6c6db1664089144543aae71c6479f08b225b2610 Mon Sep 17 00:00:00 2001 From: edX requirements bot Date: Tue, 21 Sep 2021 07:15:18 +0500 Subject: [PATCH 71/80] chore: Updating Python Requirements --- requirements/ci.txt | 4 ++-- requirements/docs.txt | 2 +- requirements/tox.txt | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements/ci.txt b/requirements/ci.txt index 6781b8f39..d9ce1d23f 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -46,9 +46,9 @@ toml==0.10.2 # via # -r requirements/tox.txt # tox -tox==3.24.3 +tox==3.24.4 # via -r requirements/tox.txt -virtualenv==20.7.2 +virtualenv==20.8.0 # via # -r requirements/tox.txt # tox diff --git a/requirements/docs.txt b/requirements/docs.txt index 962e54116..c7bf994ca 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -10,7 +10,7 @@ babel==2.9.1 # via sphinx certifi==2021.5.30 # via requests -charset-normalizer==2.0.4 +charset-normalizer==2.0.6 # via requests docutils==0.17.1 # via sphinx diff --git a/requirements/tox.txt b/requirements/tox.txt index db0ae771f..8aa220ded 100644 --- a/requirements/tox.txt +++ b/requirements/tox.txt @@ -28,7 +28,7 @@ six==1.16.0 # virtualenv toml==0.10.2 # via tox -tox==3.24.3 +tox==3.24.4 # via -r requirements/tox.in -virtualenv==20.7.2 +virtualenv==20.8.0 # via tox From 9d921b2393f80dfe7956c79f4d38240642c68d95 Mon Sep 17 00:00:00 2001 From: edX requirements bot Date: Tue, 28 Sep 2021 07:13:56 +0500 Subject: [PATCH 72/80] chore: Updating Python Requirements --- requirements/base.txt | 2 +- requirements/ci.txt | 8 ++++---- requirements/docs.txt | 2 +- requirements/pip.txt | 2 +- requirements/pip_tools.txt | 2 +- requirements/test.txt | 2 +- requirements/tox.txt | 8 ++++---- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index d1c3ba0f7..443b98861 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -16,7 +16,7 @@ django-classy-tags==2.0.0 # via django-sekizai django-js-asset==1.2.2 # via django-mptt -django-mptt==0.13.3 +django-mptt==0.13.4 # via -r requirements/base.in django-sekizai==2.0.0 # via -r requirements/base.in diff --git a/requirements/ci.txt b/requirements/ci.txt index d9ce1d23f..05fceb36e 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -8,11 +8,11 @@ backports.entry-points-selectable==1.1.0 # via # -r requirements/tox.txt # virtualenv -distlib==0.3.2 +distlib==0.3.3 # via # -r requirements/tox.txt # virtualenv -filelock==3.0.12 +filelock==3.1.0 # via # -r requirements/tox.txt # tox @@ -21,7 +21,7 @@ packaging==21.0 # via # -r requirements/tox.txt # tox -platformdirs==2.3.0 +platformdirs==2.4.0 # via # -r requirements/tox.txt # virtualenv @@ -48,7 +48,7 @@ toml==0.10.2 # tox tox==3.24.4 # via -r requirements/tox.txt -virtualenv==20.8.0 +virtualenv==20.8.1 # via # -r requirements/tox.txt # tox diff --git a/requirements/docs.txt b/requirements/docs.txt index c7bf994ca..0ebc47de6 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -54,7 +54,7 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -urllib3==1.26.6 +urllib3==1.26.7 # via requests # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/pip.txt b/requirements/pip.txt index ca02776da..a5842e713 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -10,5 +10,5 @@ wheel==0.37.0 # The following packages are considered to be unsafe in a requirements file: pip==21.2.4 # via -r requirements/pip.in -setuptools==58.0.4 +setuptools==58.1.0 # via -r requirements/pip.in diff --git a/requirements/pip_tools.txt b/requirements/pip_tools.txt index ac3012fad..a0f829531 100644 --- a/requirements/pip_tools.txt +++ b/requirements/pip_tools.txt @@ -8,7 +8,7 @@ click==8.0.1 # via pip-tools pep517==0.11.0 # via pip-tools -pip-tools==6.2.0 +pip-tools==6.3.0 # via -r requirements/pip_tools.in tomli==1.2.1 # via pep517 diff --git a/requirements/test.txt b/requirements/test.txt index 600eb7494..71e3316ad 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -23,7 +23,7 @@ django-js-asset==1.2.2 # via # -r requirements/base.txt # django-mptt -django-mptt==0.13.3 +django-mptt==0.13.4 # via -r requirements/base.txt django-sekizai==2.0.0 # via -r requirements/base.txt diff --git a/requirements/tox.txt b/requirements/tox.txt index 8aa220ded..39792667b 100644 --- a/requirements/tox.txt +++ b/requirements/tox.txt @@ -6,15 +6,15 @@ # backports.entry-points-selectable==1.1.0 # via virtualenv -distlib==0.3.2 +distlib==0.3.3 # via virtualenv -filelock==3.0.12 +filelock==3.1.0 # via # tox # virtualenv packaging==21.0 # via tox -platformdirs==2.3.0 +platformdirs==2.4.0 # via virtualenv pluggy==1.0.0 # via tox @@ -30,5 +30,5 @@ toml==0.10.2 # via tox tox==3.24.4 # via -r requirements/tox.in -virtualenv==20.8.0 +virtualenv==20.8.1 # via tox From 2775d9819b7ada317273500ceaa8ae523f2a0074 Mon Sep 17 00:00:00 2001 From: edX requirements bot Date: Tue, 5 Oct 2021 07:13:15 +0500 Subject: [PATCH 73/80] chore: Updating Python Requirements --- requirements/base.txt | 2 +- requirements/ci.txt | 2 +- requirements/docs.txt | 4 ++-- requirements/pip.txt | 2 +- requirements/test.txt | 12 ++++++------ requirements/tox.txt | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 443b98861..f0e73bebd 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -26,7 +26,7 @@ packaging==21.0 # via bleach pyparsing==2.4.7 # via packaging -pytz==2021.1 +pytz==2021.3 # via django six==1.16.0 # via bleach diff --git a/requirements/ci.txt b/requirements/ci.txt index 05fceb36e..5f4f47335 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -12,7 +12,7 @@ distlib==0.3.3 # via # -r requirements/tox.txt # virtualenv -filelock==3.1.0 +filelock==3.3.0 # via # -r requirements/tox.txt # tox diff --git a/requirements/docs.txt b/requirements/docs.txt index 0ebc47de6..bc6dbb08a 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -20,7 +20,7 @@ idna==3.2 # via requests imagesize==1.2.0 # via sphinx -jinja2==3.0.1 +jinja2==3.0.2 # via sphinx markupsafe==2.0.1 # via jinja2 @@ -30,7 +30,7 @@ pygments==2.10.0 # via sphinx pyparsing==2.4.7 # via packaging -pytz==2021.1 +pytz==2021.3 # via babel requests==2.26.0 # via sphinx diff --git a/requirements/pip.txt b/requirements/pip.txt index a5842e713..d6eeb9316 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -10,5 +10,5 @@ wheel==0.37.0 # The following packages are considered to be unsafe in a requirements file: pip==21.2.4 # via -r requirements/pip.in -setuptools==58.1.0 +setuptools==58.2.0 # via -r requirements/pip.in diff --git a/requirements/test.txt b/requirements/test.txt index 71e3316ad..2b09e5fd8 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -8,7 +8,7 @@ attrs==21.2.0 # via pytest bleach==4.1.0 # via -r requirements/base.txt -coverage==5.5 +coverage[toml]==6.0 # via pytest-cov # via # -c requirements/constraints.txt @@ -49,11 +49,11 @@ pytest==6.2.5 # -r requirements/test.in # pytest-cov # pytest-django -pytest-cov==2.12.1 +pytest-cov==3.0.0 # via -r requirements/test.in pytest-django==4.4.0 # via -r requirements/test.in -pytz==2021.1 +pytz==2021.3 # via # -r requirements/base.txt # django @@ -68,9 +68,9 @@ sqlparse==0.4.2 # -r requirements/base.txt # django toml==0.10.2 - # via - # pytest - # pytest-cov + # via pytest +tomli==1.2.1 + # via coverage webencodings==0.5.1 # via # -r requirements/base.txt diff --git a/requirements/tox.txt b/requirements/tox.txt index 39792667b..04420fe9e 100644 --- a/requirements/tox.txt +++ b/requirements/tox.txt @@ -8,7 +8,7 @@ backports.entry-points-selectable==1.1.0 # via virtualenv distlib==0.3.3 # via virtualenv -filelock==3.1.0 +filelock==3.3.0 # via # tox # virtualenv From 3bd9307a44aa66a7f2fa7ffaa0f07caf2e98478d Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Thu, 7 Oct 2021 13:53:35 -0400 Subject: [PATCH 74/80] build: use the organization commitlint check --- .github/workflows/commitlint.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/workflows/commitlint.yml diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml new file mode 100644 index 000000000..e2b066153 --- /dev/null +++ b/.github/workflows/commitlint.yml @@ -0,0 +1,10 @@ +# Run commitlint on the commit messages in a pull request. + +name: Lint Commit Messages + +on: + - pull_request + +jobs: + commitlint: + uses: edx/.github/.github/workflows/commitlint.yml@master From 1fd492cafbfac3de4a32f0f3eedd292c06db99a5 Mon Sep 17 00:00:00 2001 From: edX requirements bot Date: Tue, 12 Oct 2021 07:15:15 +0500 Subject: [PATCH 75/80] chore: Updating Python Requirements --- requirements/docs.txt | 4 ++-- requirements/pip.txt | 2 +- requirements/pip_tools.txt | 4 ++-- requirements/test.txt | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements/docs.txt b/requirements/docs.txt index bc6dbb08a..ad2bb21c4 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -8,9 +8,9 @@ alabaster==0.7.12 # via sphinx babel==2.9.1 # via sphinx -certifi==2021.5.30 +certifi==2021.10.8 # via requests -charset-normalizer==2.0.6 +charset-normalizer==2.0.7 # via requests docutils==0.17.1 # via sphinx diff --git a/requirements/pip.txt b/requirements/pip.txt index d6eeb9316..0b88fedce 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -8,7 +8,7 @@ wheel==0.37.0 # via -r requirements/pip.in # The following packages are considered to be unsafe in a requirements file: -pip==21.2.4 +pip==21.3 # via -r requirements/pip.in setuptools==58.2.0 # via -r requirements/pip.in diff --git a/requirements/pip_tools.txt b/requirements/pip_tools.txt index a0f829531..3b58a32b9 100644 --- a/requirements/pip_tools.txt +++ b/requirements/pip_tools.txt @@ -4,11 +4,11 @@ # # make upgrade # -click==8.0.1 +click==8.0.3 # via pip-tools pep517==0.11.0 # via pip-tools -pip-tools==6.3.0 +pip-tools==6.3.1 # via -r requirements/pip_tools.in tomli==1.2.1 # via pep517 diff --git a/requirements/test.txt b/requirements/test.txt index 2b09e5fd8..8ad898c6f 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -8,7 +8,7 @@ attrs==21.2.0 # via pytest bleach==4.1.0 # via -r requirements/base.txt -coverage[toml]==6.0 +coverage[toml]==6.0.2 # via pytest-cov # via # -c requirements/constraints.txt From 0acdb16439e2e0156d8d0bb71591d63d3135a466 Mon Sep 17 00:00:00 2001 From: edX requirements bot <49161187+edx-requirements-bot@users.noreply.github.com> Date: Tue, 19 Oct 2021 22:01:58 +0500 Subject: [PATCH 76/80] chore: Updating Python Requirements (#84) --- requirements/ci.txt | 2 +- requirements/docs.txt | 2 +- requirements/pip_tools.txt | 4 ++-- requirements/tox.txt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements/ci.txt b/requirements/ci.txt index 5f4f47335..b52cb1f79 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -12,7 +12,7 @@ distlib==0.3.3 # via # -r requirements/tox.txt # virtualenv -filelock==3.3.0 +filelock==3.3.1 # via # -r requirements/tox.txt # tox diff --git a/requirements/docs.txt b/requirements/docs.txt index ad2bb21c4..8c1dea766 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -16,7 +16,7 @@ docutils==0.17.1 # via sphinx edx-sphinx-theme==3.0.0 # via -r requirements/docs.in -idna==3.2 +idna==3.3 # via requests imagesize==1.2.0 # via sphinx diff --git a/requirements/pip_tools.txt b/requirements/pip_tools.txt index 3b58a32b9..ae77653b8 100644 --- a/requirements/pip_tools.txt +++ b/requirements/pip_tools.txt @@ -6,9 +6,9 @@ # click==8.0.3 # via pip-tools -pep517==0.11.0 +pep517==0.12.0 # via pip-tools -pip-tools==6.3.1 +pip-tools==6.4.0 # via -r requirements/pip_tools.in tomli==1.2.1 # via pep517 diff --git a/requirements/tox.txt b/requirements/tox.txt index 04420fe9e..b0775678a 100644 --- a/requirements/tox.txt +++ b/requirements/tox.txt @@ -8,7 +8,7 @@ backports.entry-points-selectable==1.1.0 # via virtualenv distlib==0.3.3 # via virtualenv -filelock==3.3.0 +filelock==3.3.1 # via # tox # virtualenv From 9808d8fe2f44bc946ce5f4a86a9ffb693ac1d741 Mon Sep 17 00:00:00 2001 From: edX requirements bot <49161187+edx-requirements-bot@users.noreply.github.com> Date: Tue, 26 Oct 2021 12:44:27 +0500 Subject: [PATCH 77/80] chore: Updating Python Requirements (#85) --- requirements/base.txt | 2 +- requirements/ci.txt | 4 ++-- requirements/docs.txt | 2 +- requirements/pip.txt | 4 ++-- requirements/pip_tools.txt | 2 +- requirements/test.txt | 4 ++-- requirements/tox.txt | 4 ++-- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index f0e73bebd..ce48c4180 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -24,7 +24,7 @@ markdown==3.3.4 # via -r requirements/base.in packaging==21.0 # via bleach -pyparsing==2.4.7 +pyparsing==3.0.1 # via packaging pytz==2021.3 # via django diff --git a/requirements/ci.txt b/requirements/ci.txt index b52cb1f79..c5d72f2e1 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -33,7 +33,7 @@ py==1.10.0 # via # -r requirements/tox.txt # tox -pyparsing==2.4.7 +pyparsing==3.0.1 # via # -r requirements/tox.txt # packaging @@ -48,7 +48,7 @@ toml==0.10.2 # tox tox==3.24.4 # via -r requirements/tox.txt -virtualenv==20.8.1 +virtualenv==20.9.0 # via # -r requirements/tox.txt # tox diff --git a/requirements/docs.txt b/requirements/docs.txt index 8c1dea766..ef7fd9a90 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -28,7 +28,7 @@ packaging==21.0 # via sphinx pygments==2.10.0 # via sphinx -pyparsing==2.4.7 +pyparsing==3.0.1 # via packaging pytz==2021.3 # via babel diff --git a/requirements/pip.txt b/requirements/pip.txt index 0b88fedce..7872e0b9f 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -8,7 +8,7 @@ wheel==0.37.0 # via -r requirements/pip.in # The following packages are considered to be unsafe in a requirements file: -pip==21.3 +pip==21.3.1 # via -r requirements/pip.in -setuptools==58.2.0 +setuptools==58.3.0 # via -r requirements/pip.in diff --git a/requirements/pip_tools.txt b/requirements/pip_tools.txt index ae77653b8..2aea4ec88 100644 --- a/requirements/pip_tools.txt +++ b/requirements/pip_tools.txt @@ -10,7 +10,7 @@ pep517==0.12.0 # via pip-tools pip-tools==6.4.0 # via -r requirements/pip_tools.in -tomli==1.2.1 +tomli==1.2.2 # via pep517 wheel==0.37.0 # via pip-tools diff --git a/requirements/test.txt b/requirements/test.txt index 8ad898c6f..69d79fb2a 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -40,7 +40,7 @@ pluggy==1.0.0 # via pytest py==1.10.0 # via pytest -pyparsing==2.4.7 +pyparsing==3.0.1 # via # -r requirements/base.txt # packaging @@ -69,7 +69,7 @@ sqlparse==0.4.2 # django toml==0.10.2 # via pytest -tomli==1.2.1 +tomli==1.2.2 # via coverage webencodings==0.5.1 # via diff --git a/requirements/tox.txt b/requirements/tox.txt index b0775678a..ec7211182 100644 --- a/requirements/tox.txt +++ b/requirements/tox.txt @@ -20,7 +20,7 @@ pluggy==1.0.0 # via tox py==1.10.0 # via tox -pyparsing==2.4.7 +pyparsing==3.0.1 # via packaging six==1.16.0 # via @@ -30,5 +30,5 @@ toml==0.10.2 # via tox tox==3.24.4 # via -r requirements/tox.in -virtualenv==20.8.1 +virtualenv==20.9.0 # via tox From 54bf670f18e9dbf9ae9408b00517c3346e3a50b8 Mon Sep 17 00:00:00 2001 From: edX requirements bot Date: Tue, 2 Nov 2021 07:14:46 +0500 Subject: [PATCH 78/80] chore: Updating Python Requirements --- requirements/base.txt | 4 ++-- requirements/ci.txt | 8 ++++---- requirements/docs.txt | 4 ++-- requirements/pip.txt | 2 +- requirements/test.txt | 6 +++--- requirements/tox.txt | 8 ++++---- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index ce48c4180..f5dee6b68 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -22,9 +22,9 @@ django-sekizai==2.0.0 # via -r requirements/base.in markdown==3.3.4 # via -r requirements/base.in -packaging==21.0 +packaging==21.2 # via bleach -pyparsing==3.0.1 +pyparsing==2.4.7 # via packaging pytz==2021.3 # via django diff --git a/requirements/ci.txt b/requirements/ci.txt index c5d72f2e1..9b4f42676 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -12,12 +12,12 @@ distlib==0.3.3 # via # -r requirements/tox.txt # virtualenv -filelock==3.3.1 +filelock==3.3.2 # via # -r requirements/tox.txt # tox # virtualenv -packaging==21.0 +packaging==21.2 # via # -r requirements/tox.txt # tox @@ -33,7 +33,7 @@ py==1.10.0 # via # -r requirements/tox.txt # tox -pyparsing==3.0.1 +pyparsing==2.4.7 # via # -r requirements/tox.txt # packaging @@ -48,7 +48,7 @@ toml==0.10.2 # tox tox==3.24.4 # via -r requirements/tox.txt -virtualenv==20.9.0 +virtualenv==20.10.0 # via # -r requirements/tox.txt # tox diff --git a/requirements/docs.txt b/requirements/docs.txt index ef7fd9a90..2cb844407 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -24,11 +24,11 @@ jinja2==3.0.2 # via sphinx markupsafe==2.0.1 # via jinja2 -packaging==21.0 +packaging==21.2 # via sphinx pygments==2.10.0 # via sphinx -pyparsing==3.0.1 +pyparsing==2.4.7 # via packaging pytz==2021.3 # via babel diff --git a/requirements/pip.txt b/requirements/pip.txt index 7872e0b9f..8a186fdb3 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -10,5 +10,5 @@ wheel==0.37.0 # The following packages are considered to be unsafe in a requirements file: pip==21.3.1 # via -r requirements/pip.in -setuptools==58.3.0 +setuptools==58.4.0 # via -r requirements/pip.in diff --git a/requirements/test.txt b/requirements/test.txt index 69d79fb2a..c99e3cecf 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -8,7 +8,7 @@ attrs==21.2.0 # via pytest bleach==4.1.0 # via -r requirements/base.txt -coverage[toml]==6.0.2 +coverage[toml]==6.1.1 # via pytest-cov # via # -c requirements/constraints.txt @@ -31,7 +31,7 @@ iniconfig==1.1.1 # via pytest markdown==3.3.4 # via -r requirements/base.txt -packaging==21.0 +packaging==21.2 # via # -r requirements/base.txt # bleach @@ -40,7 +40,7 @@ pluggy==1.0.0 # via pytest py==1.10.0 # via pytest -pyparsing==3.0.1 +pyparsing==2.4.7 # via # -r requirements/base.txt # packaging diff --git a/requirements/tox.txt b/requirements/tox.txt index ec7211182..00fa64218 100644 --- a/requirements/tox.txt +++ b/requirements/tox.txt @@ -8,11 +8,11 @@ backports.entry-points-selectable==1.1.0 # via virtualenv distlib==0.3.3 # via virtualenv -filelock==3.3.1 +filelock==3.3.2 # via # tox # virtualenv -packaging==21.0 +packaging==21.2 # via tox platformdirs==2.4.0 # via virtualenv @@ -20,7 +20,7 @@ pluggy==1.0.0 # via tox py==1.10.0 # via tox -pyparsing==3.0.1 +pyparsing==2.4.7 # via packaging six==1.16.0 # via @@ -30,5 +30,5 @@ toml==0.10.2 # via tox tox==3.24.4 # via -r requirements/tox.in -virtualenv==20.9.0 +virtualenv==20.10.0 # via tox From 13c92683a9f4640157e4655b1f959a8e3108d952 Mon Sep 17 00:00:00 2001 From: edX requirements bot <49161187+edx-requirements-bot@users.noreply.github.com> Date: Tue, 9 Nov 2021 12:24:46 +0500 Subject: [PATCH 79/80] chore: Updating Python Requirements (#87) --- requirements/ci.txt | 2 +- requirements/pip.txt | 2 +- requirements/test.txt | 2 +- requirements/tox.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements/ci.txt b/requirements/ci.txt index 9b4f42676..2f39271a7 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -29,7 +29,7 @@ pluggy==1.0.0 # via # -r requirements/tox.txt # tox -py==1.10.0 +py==1.11.0 # via # -r requirements/tox.txt # tox diff --git a/requirements/pip.txt b/requirements/pip.txt index 8a186fdb3..ac73ce516 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -10,5 +10,5 @@ wheel==0.37.0 # The following packages are considered to be unsafe in a requirements file: pip==21.3.1 # via -r requirements/pip.in -setuptools==58.4.0 +setuptools==58.5.3 # via -r requirements/pip.in diff --git a/requirements/test.txt b/requirements/test.txt index c99e3cecf..4db817306 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -38,7 +38,7 @@ packaging==21.2 # pytest pluggy==1.0.0 # via pytest -py==1.10.0 +py==1.11.0 # via pytest pyparsing==2.4.7 # via diff --git a/requirements/tox.txt b/requirements/tox.txt index 00fa64218..3ed4242de 100644 --- a/requirements/tox.txt +++ b/requirements/tox.txt @@ -18,7 +18,7 @@ platformdirs==2.4.0 # via virtualenv pluggy==1.0.0 # via tox -py==1.10.0 +py==1.11.0 # via tox pyparsing==2.4.7 # via packaging From f0dd7e826e59741c4b9786c045be4f42a2be62cc Mon Sep 17 00:00:00 2001 From: edX requirements bot Date: Tue, 9 Nov 2021 18:59:38 +0500 Subject: [PATCH 80/80] feat: advertise constraints in setup.py --- MANIFEST.in | 1 + setup.py | 62 +++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 79de37839..38b8aab96 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,3 +6,4 @@ recursive-include wiki *.html *.png *.gif *js *.css *jpg *jpeg *svg *py *.txt *. recursive-include django_notify *.html *.png *.gif *js *.css *jpg *jpeg *svg *py *.txt *.json include requirements/base.in +include requirements/constraints.txt diff --git a/setup.py b/setup.py index 1fdad0774..d4cb02c9d 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ import os +import re from setuptools import find_packages, setup @@ -21,24 +22,67 @@ def build_media_pattern(base_folder, file_extension): def load_requirements(*requirements_paths): """ Load all requirements from the specified requirements files. + + Requirements will include any constraints from files specified + with -c in the requirements files. Returns a list of requirement strings. """ - requirements = set() + # UPDATED VIA SEMGREP - if you need to remove/modify this method remove this line and add a comment specifying why. + + requirements = {} + constraint_files = set() + + # groups "my-package-name<=x.y.z,..." into ("my-package-name", "<=x.y.z,...") + requirement_line_regex = re.compile(r"([a-zA-Z0-9-_.]+)([<>=][^#\s]+)?") + + def add_version_constraint_or_raise(current_line, current_requirements, add_if_not_present): + regex_match = requirement_line_regex.match(current_line) + if regex_match: + package = regex_match.group(1) + version_constraints = regex_match.group(2) + existing_version_constraints = current_requirements.get(package, None) + # it's fine to add constraints to an unconstrained package, but raise an error if there are already + # constraints in place + if existing_version_constraints and existing_version_constraints != version_constraints: + raise BaseException(f'Multiple constraint definitions found for {package}:' + f' "{existing_version_constraints}" and "{version_constraints}".' + f'Combine constraints into one location with {package}' + f'{existing_version_constraints},{version_constraints}.') + if add_if_not_present or package in current_requirements: + current_requirements[package] = version_constraints + + # process .in files and store the path to any constraint files that are pulled in for path in requirements_paths: with open(path) as reqs: - requirements.update( - line.split('#')[0].strip() for line in reqs - if is_requirement(line.strip()) - ) - return list(requirements) + for line in reqs: + if is_requirement(line): + add_version_constraint_or_raise(line, requirements, True) + if line and line.startswith('-c') and not line.startswith('-c http'): + constraint_files.add(os.path.dirname(path) + '/' + line.split('#')[0].replace('-c', '').strip()) + + # process constraint files and add any new constraints found to existing requirements + for constraint_file in constraint_files: + with open(constraint_file) as reader: + for line in reader: + if is_requirement(line): + add_version_constraint_or_raise(line, requirements, False) + + # process back into list of pkg><=constraints strings + constrained_requirements = [f'{pkg}{version or ""}' for (pkg, version) in sorted(requirements.items())] + return constrained_requirements def is_requirement(line): """ - Return True if the requirement line is a package requirement; - that is, it is not blank, a comment, a URL, or an included file. + Return True if the requirement line is a package requirement. + + Returns: + bool: True if the line is not blank, a comment, + a URL, or an included file """ - return line and not line.startswith(('-r', '#', '-e', 'git+', '-c')) + # UPDATED VIA SEMGREP - if you need to remove/modify this method remove this line and add a comment specifying why + + return line and line.strip() and not line.startswith(('-r', '#', '-e', 'git+', '-c')) template_patterns = (
  • %s