Commit 17f975b7 by bridger

Merge pull request #168 from MITx/multicourse_wiki

Multicourse wiki
parents 6e90a067 db27b2ed
......@@ -30,6 +30,10 @@ class CourseDescriptor(SequenceDescriptor):
def instructors(self):
return self.get_about_section("instructors").split("\n")
@property
def wiki_namespace(self):
return self.location.course
def get_about_section(self, section_key):
"""
This returns the snippet of html to be rendered on the course about page, given the key for the section.
......
......@@ -14,6 +14,7 @@ _FIELDS = ['number', # 6.002x
'path', # /some/absolute/filepath/6.002x --> course.xml is in here.
'instructors', # ['Anant Agarwal']
'institution', # "MIT"
'wiki_namespace',
'grader', # a courseware.graders.CourseGrader object
#'start', # These should be datetime fields
......
......@@ -34,7 +34,7 @@ class ArticleAdminForm(forms.ModelForm):
model = Article
class ArticleAdmin(admin.ModelAdmin):
list_display = ('created_by', 'slug', 'modified_on', 'parent')
list_display = ('created_by', 'slug', 'modified_on', 'namespace')
search_fields = ('slug',)
prepopulated_fields = {'slug': ('title',) }
inlines = [RevisionInline]
......
......@@ -17,8 +17,6 @@ circuit-schematic:[["r",[128,48,0],{"r":"1","_json_":0},["2","1"]],["view",0,0,2
import markdown
import re
import simplewiki.settings as settings
from django.utils.html import escape
try:
......@@ -68,5 +66,7 @@ class CircuitLink(markdown.inlinepatterns.Pattern):
return etree.fromstring("<div align='center'><input type='hidden' parts='' value='" + data + "' analyses='' class='schematic ctrls' width='640' height='480'/></div>")
def makeExtension(configs=None) :
return CircuitExtension(configs=configs)
def makeExtension(configs=None):
to_return = CircuitExtension(configs=configs)
print "circuit returning " , to_return
return to_return
......@@ -33,7 +33,7 @@ class WikiPathExtension(markdown.Extension):
def __init__(self, configs):
# set extension defaults
self.config = {
'base_url' : ['/', 'String to append to beginning or URL.'],
'default_namespace' : ['edX', 'Default namespace for when one isn\'t specified.'],
'html_class' : ['wikipath', 'CSS hook. Leave blank for none.']
}
......@@ -62,7 +62,10 @@ class WikiPath(markdown.inlinepatterns.Pattern):
if article_title.startswith("/"):
article_title = article_title[1:]
url = self.config['base_url'][0] + article_title
if not "/" in article_title:
article_title = self.config['default_namespace'][0] + "/" + article_title
url = "../" + article_title
label = m.group('linkTitle')
a = etree.Element('a')
a.set('href', url)
......
# -*- 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 'Article'
db.create_table('simplewiki_article', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('title', self.gf('django.db.models.fields.CharField')(max_length=512)),
('slug', self.gf('django.db.models.fields.SlugField')(max_length=100, blank=True)),
('created_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
('created_on', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=1, blank=True)),
('modified_on', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=1, blank=True)),
('parent', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['simplewiki.Article'], null=True, blank=True)),
('locked', self.gf('django.db.models.fields.BooleanField')(default=False)),
('permissions', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['simplewiki.Permission'], null=True, blank=True)),
('current_revision', self.gf('django.db.models.fields.related.OneToOneField')(blank=True, related_name='current_rev', unique=True, null=True, to=orm['simplewiki.Revision'])),
))
db.send_create_signal('simplewiki', ['Article'])
# Adding unique constraint on 'Article', fields ['slug', 'parent']
db.create_unique('simplewiki_article', ['slug', 'parent_id'])
# Adding M2M table for field related on 'Article'
db.create_table('simplewiki_article_related', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('from_article', models.ForeignKey(orm['simplewiki.article'], null=False)),
('to_article', models.ForeignKey(orm['simplewiki.article'], null=False))
))
db.create_unique('simplewiki_article_related', ['from_article_id', 'to_article_id'])
# Adding model 'ArticleAttachment'
db.create_table('simplewiki_articleattachment', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('article', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['simplewiki.Article'])),
('file', self.gf('django.db.models.fields.files.FileField')(max_length=255)),
('uploaded_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
('uploaded_on', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
))
db.send_create_signal('simplewiki', ['ArticleAttachment'])
# Adding model 'Revision'
db.create_table('simplewiki_revision', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('article', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['simplewiki.Article'])),
('revision_text', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
('revision_user', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='wiki_revision_user', null=True, to=orm['auth.User'])),
('revision_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('contents', self.gf('django.db.models.fields.TextField')()),
('contents_parsed', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
('counter', self.gf('django.db.models.fields.IntegerField')(default=1)),
('previous_revision', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['simplewiki.Revision'], null=True, blank=True)),
('deleted', self.gf('django.db.models.fields.IntegerField')(default=0)),
))
db.send_create_signal('simplewiki', ['Revision'])
# Adding model 'Permission'
db.create_table('simplewiki_permission', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('permission_name', self.gf('django.db.models.fields.CharField')(max_length=255)),
))
db.send_create_signal('simplewiki', ['Permission'])
# Adding M2M table for field can_write on 'Permission'
db.create_table('simplewiki_permission_can_write', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('permission', models.ForeignKey(orm['simplewiki.permission'], null=False)),
('user', models.ForeignKey(orm['auth.user'], null=False))
))
db.create_unique('simplewiki_permission_can_write', ['permission_id', 'user_id'])
# Adding M2M table for field can_read on 'Permission'
db.create_table('simplewiki_permission_can_read', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('permission', models.ForeignKey(orm['simplewiki.permission'], null=False)),
('user', models.ForeignKey(orm['auth.user'], null=False))
))
db.create_unique('simplewiki_permission_can_read', ['permission_id', 'user_id'])
def backwards(self, orm):
# Removing unique constraint on 'Article', fields ['slug', 'parent']
db.delete_unique('simplewiki_article', ['slug', 'parent_id'])
# Deleting model 'Article'
db.delete_table('simplewiki_article')
# Removing M2M table for field related on 'Article'
db.delete_table('simplewiki_article_related')
# Deleting model 'ArticleAttachment'
db.delete_table('simplewiki_articleattachment')
# Deleting model 'Revision'
db.delete_table('simplewiki_revision')
# Deleting model 'Permission'
db.delete_table('simplewiki_permission')
# Removing M2M table for field can_write on 'Permission'
db.delete_table('simplewiki_permission_can_write')
# Removing M2M table for field can_read on 'Permission'
db.delete_table('simplewiki_permission_can_read')
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'},
'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),
'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}),
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': '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'}),
'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
'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'}),
'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
},
'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'})
},
'simplewiki.article': {
'Meta': {'unique_together': "(('slug', 'parent'),)", 'object_name': 'Article'},
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_rev'", 'unique': 'True', 'null': 'True', 'to': "orm['simplewiki.Revision']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'modified_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']", 'null': 'True', 'blank': 'True'}),
'permissions': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Permission']", 'null': 'True', 'blank': 'True'}),
'related': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'related_rel_+'", 'null': 'True', 'to': "orm['simplewiki.Article']"}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'blank': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '512'})
},
'simplewiki.articleattachment': {
'Meta': {'object_name': 'ArticleAttachment'},
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
'file': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
},
'simplewiki.permission': {
'Meta': {'object_name': 'Permission'},
'can_read': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'can_write': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'write'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'permission_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
},
'simplewiki.revision': {
'Meta': {'object_name': 'Revision'},
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
'contents': ('django.db.models.fields.TextField', [], {}),
'contents_parsed': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'counter': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
'deleted': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Revision']", 'null': 'True', 'blank': 'True'}),
'revision_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'revision_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'revision_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'wiki_revision_user'", 'null': 'True', 'to': "orm['auth.User']"})
}
}
complete_apps = ['simplewiki']
\ No newline at end of file
# -*- 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):
# We collect every article slug in a set. Any time we see a duplicate, we change the duplicate's name
unique_slugs = set()
for article in orm.Article.objects.all():
if article.slug in unique_slugs:
i = 2
new_name = article.slug + str(i)
while new_name in unique_slugs:
i += 1
new_name = article.slug + str(i)
print "Changing" , article.slug , "to" , new_name
article.slug = new_name
article.save()
unique_slugs.add( article.slug )
def backwards(self, orm):
"Write your backwards methods here."
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'},
'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),
'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}),
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': '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'}),
'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
'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'}),
'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
},
'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'})
},
'simplewiki.article': {
'Meta': {'unique_together': "(('slug', 'parent'),)", 'object_name': 'Article'},
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_rev'", 'unique': 'True', 'null': 'True', 'to': "orm['simplewiki.Revision']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'modified_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']", 'null': 'True', 'blank': 'True'}),
'permissions': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Permission']", 'null': 'True', 'blank': 'True'}),
'related': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'related_rel_+'", 'null': 'True', 'to': "orm['simplewiki.Article']"}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'blank': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '512'})
},
'simplewiki.articleattachment': {
'Meta': {'object_name': 'ArticleAttachment'},
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
'file': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
},
'simplewiki.permission': {
'Meta': {'object_name': 'Permission'},
'can_read': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'can_write': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'write'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'permission_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
},
'simplewiki.revision': {
'Meta': {'object_name': 'Revision'},
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
'contents': ('django.db.models.fields.TextField', [], {}),
'contents_parsed': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'counter': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
'deleted': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Revision']", 'null': 'True', 'blank': 'True'}),
'revision_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'revision_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'revision_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'wiki_revision_user'", 'null': 'True', 'to': "orm['auth.User']"})
}
}
complete_apps = ['simplewiki']
symmetrical = True
# -*- 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):
# Removing unique constraint on 'Article', fields ['slug', 'parent']
db.delete_unique('simplewiki_article', ['slug', 'parent_id'])
# Adding model 'Namespace'
db.create_table('simplewiki_namespace', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=30)),
))
db.send_create_signal('simplewiki', ['Namespace'])
# Deleting field 'Article.parent'
db.delete_column('simplewiki_article', 'parent_id')
# Adding field 'Article.namespace'
db.add_column('simplewiki_article', 'namespace',
self.gf('django.db.models.fields.related.ForeignKey')(default=1, to=orm['simplewiki.Namespace']),
keep_default=False)
# Adding unique constraint on 'Article', fields ['namespace', 'slug']
db.create_unique('simplewiki_article', ['namespace_id', 'slug'])
def backwards(self, orm):
# Removing unique constraint on 'Article', fields ['namespace', 'slug']
db.delete_unique('simplewiki_article', ['namespace_id', 'slug'])
# Deleting model 'Namespace'
db.delete_table('simplewiki_namespace')
# Adding field 'Article.parent'
db.add_column('simplewiki_article', 'parent',
self.gf('django.db.models.fields.related.ForeignKey')(to=orm['simplewiki.Article'], null=True, blank=True),
keep_default=False)
# Deleting field 'Article.namespace'
db.delete_column('simplewiki_article', 'namespace_id')
# Adding unique constraint on 'Article', fields ['slug', 'parent']
db.create_unique('simplewiki_article', ['slug', 'parent_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'},
'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),
'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}),
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': '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'}),
'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
'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'}),
'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
},
'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'})
},
'simplewiki.article': {
'Meta': {'unique_together': "(('slug', 'namespace'),)", 'object_name': 'Article'},
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_rev'", 'unique': 'True', 'null': 'True', 'to': "orm['simplewiki.Revision']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'modified_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
'namespace': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Namespace']"}),
'permissions': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Permission']", 'null': 'True', 'blank': 'True'}),
'related': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'related_rel_+'", 'null': 'True', 'to': "orm['simplewiki.Article']"}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'blank': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '512'})
},
'simplewiki.articleattachment': {
'Meta': {'object_name': 'ArticleAttachment'},
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
'file': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
},
'simplewiki.namespace': {
'Meta': {'object_name': 'Namespace'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '30'})
},
'simplewiki.permission': {
'Meta': {'object_name': 'Permission'},
'can_read': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'can_write': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'write'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'permission_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
},
'simplewiki.revision': {
'Meta': {'object_name': 'Revision'},
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
'contents': ('django.db.models.fields.TextField', [], {}),
'contents_parsed': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'counter': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
'deleted': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Revision']", 'null': 'True', 'blank': 'True'}),
'revision_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'revision_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'revision_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'wiki_revision_user'", 'null': 'True', 'to': "orm['auth.User']"})
}
}
complete_apps = ['simplewiki']
\ No newline at end of file
# -*- 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):
namespace6002x, created = orm.Namespace.objects.get_or_create(name="6.002xS12")
if created:
namespace6002x.save()
for article in orm.Article.objects.all():
article.namespace = namespace6002x
article.save()
def backwards(self, orm):
raise RuntimeError("Cannot reverse this migration.")
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'},
'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),
'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}),
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': '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'}),
'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
'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'}),
'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
},
'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'})
},
'simplewiki.article': {
'Meta': {'unique_together': "(('slug', 'namespace'),)", 'object_name': 'Article'},
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_rev'", 'unique': 'True', 'null': 'True', 'to': "orm['simplewiki.Revision']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'modified_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
'namespace': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Namespace']"}),
'permissions': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Permission']", 'null': 'True', 'blank': 'True'}),
'related': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'related_rel_+'", 'null': 'True', 'to': "orm['simplewiki.Article']"}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'blank': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '512'})
},
'simplewiki.articleattachment': {
'Meta': {'object_name': 'ArticleAttachment'},
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
'file': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
},
'simplewiki.namespace': {
'Meta': {'object_name': 'Namespace'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '30'})
},
'simplewiki.permission': {
'Meta': {'object_name': 'Permission'},
'can_read': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'can_write': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'write'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'permission_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
},
'simplewiki.revision': {
'Meta': {'object_name': 'Revision'},
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
'contents': ('django.db.models.fields.TextField', [], {}),
'contents_parsed': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'counter': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
'deleted': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Revision']", 'null': 'True', 'blank': 'True'}),
'revision_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'revision_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'revision_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'wiki_revision_user'", 'null': 'True', 'to': "orm['auth.User']"})
}
}
complete_apps = ['simplewiki']
symmetrical = True
# -*- 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 unique constraint on 'Namespace', fields ['name']
db.create_unique('simplewiki_namespace', ['name'])
def backwards(self, orm):
# Removing unique constraint on 'Namespace', fields ['name']
db.delete_unique('simplewiki_namespace', ['name'])
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'},
'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),
'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}),
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': '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'}),
'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
'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'}),
'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
},
'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'})
},
'simplewiki.article': {
'Meta': {'unique_together': "(('slug', 'namespace'),)", 'object_name': 'Article'},
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_rev'", 'unique': 'True', 'null': 'True', 'to': "orm['simplewiki.Revision']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'modified_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
'namespace': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Namespace']"}),
'permissions': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Permission']", 'null': 'True', 'blank': 'True'}),
'related': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'related_rel_+'", 'null': 'True', 'to': "orm['simplewiki.Article']"}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'blank': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '512'})
},
'simplewiki.articleattachment': {
'Meta': {'object_name': 'ArticleAttachment'},
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
'file': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
},
'simplewiki.namespace': {
'Meta': {'object_name': 'Namespace'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'simplewiki.permission': {
'Meta': {'object_name': 'Permission'},
'can_read': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'can_write': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'write'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'permission_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
},
'simplewiki.revision': {
'Meta': {'object_name': 'Revision'},
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
'contents': ('django.db.models.fields.TextField', [], {}),
'contents_parsed': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'counter': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
'deleted': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Revision']", 'null': 'True', 'blank': 'True'}),
'revision_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'revision_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'revision_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'wiki_revision_user'", 'null': 'True', 'to': "orm['auth.User']"})
}
}
complete_apps = ['simplewiki']
\ No newline at end of file
# -*- 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 index on 'Namespace', fields ['name']
db.create_index('simplewiki_namespace', ['name'])
def backwards(self, orm):
# Removing index on 'Namespace', fields ['name']
db.delete_index('simplewiki_namespace', ['name'])
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'},
'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),
'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}),
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': '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'}),
'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
'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'}),
'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
},
'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'})
},
'simplewiki.article': {
'Meta': {'unique_together': "(('slug', 'namespace'),)", 'object_name': 'Article'},
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_rev'", 'unique': 'True', 'null': 'True', 'to': "orm['simplewiki.Revision']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'modified_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}),
'namespace': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Namespace']"}),
'permissions': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Permission']", 'null': 'True', 'blank': 'True'}),
'related': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'related_rel_+'", 'null': 'True', 'to': "orm['simplewiki.Article']"}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'blank': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '512'})
},
'simplewiki.articleattachment': {
'Meta': {'object_name': 'ArticleAttachment'},
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
'file': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
},
'simplewiki.namespace': {
'Meta': {'object_name': 'Namespace'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30', 'db_index': 'True'})
},
'simplewiki.permission': {
'Meta': {'object_name': 'Permission'},
'can_read': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'can_write': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'write'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'permission_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
},
'simplewiki.revision': {
'Meta': {'object_name': 'Revision'},
'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}),
'contents': ('django.db.models.fields.TextField', [], {}),
'contents_parsed': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'counter': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
'deleted': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Revision']", 'null': 'True', 'blank': 'True'}),
'revision_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'revision_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'revision_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'wiki_revision_user'", 'null': 'True', 'to': "orm['auth.User']"})
}
}
complete_apps = ['simplewiki']
\ No newline at end of file
import difflib
import os
from django import forms
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.db import models
from django.db.models import signals
from django.utils.translation import ugettext_lazy as _
from markdown import markdown
from wiki_settings import *
from util.cache import cache
class ShouldHaveExactlyOneRootSlug(Exception):
pass
class Article(models.Model):
"""Wiki article referring to Revision model for actual content.
'slug' and 'parent' field should be maintained centrally, since users
aren't allowed to change them, anyways.
"""
title = models.CharField(max_length=512, verbose_name=_('Article title'),
blank=False)
slug = models.SlugField(max_length=100, verbose_name=_('slug'),
help_text=_('Letters, numbers, underscore and hyphen.'),
blank=True)
created_by = models.ForeignKey(User, verbose_name=_('Created by'), blank=True, null=True)
created_on = models.DateTimeField(auto_now_add = 1)
modified_on = models.DateTimeField(auto_now_add = 1)
parent = models.ForeignKey('self', verbose_name=_('Parent article slug'),
help_text=_('Affects URL structure and possibly inherits permissions'),
null=True, blank=True)
locked = models.BooleanField(default=False, verbose_name=_('Locked for editing'))
permissions = models.ForeignKey('Permission', verbose_name=_('Permissions'),
blank=True, null=True,
help_text=_('Permission group'))
current_revision = models.OneToOneField('Revision', related_name='current_rev',
blank=True, null=True, editable=True)
related = models.ManyToManyField('self', verbose_name=_('Related articles'), symmetrical=True,
help_text=_('Sets a symmetrical relation other articles'),
blank=True, null=True)
def attachments(self):
return ArticleAttachment.objects.filter(article__exact = self)
@classmethod
def get_root(cls):
"""Return the root article, which should ALWAYS exist..
except the very first time the wiki is loaded, in which
case the user is prompted to create this article."""
try:
return Article.objects.filter(slug__exact = "")[0]
except:
raise ShouldHaveExactlyOneRootSlug()
def get_url(self):
"""Return the Wiki URL for an article"""
url = self.slug + "/"
if self.parent_id:
parent_url = cache.get("wiki_url-" + str(self.parent_id))
if parent_url is None:
parent_url = self.parent.get_url()
url = parent_url + url
cache.set("wiki_url-" + str(self.id), url, 60*60)
return url
def get_abs_url(self):
"""Return the absolute path for an article. This is necessary in cases
where the template system isn't used for generating URLs..."""
# TODO: Remove and create a reverse() lookup.
return WIKI_BASE + self.get_url()
@models.permalink
def get_absolute_url(self):
return ('wiki_view', [self.get_url()])
@classmethod
def get_url_reverse(cls, path, article, return_list=[]):
"""Lookup a URL and return the corresponding set of articles
in the path."""
if path == []:
return return_list + [article]
# Lookup next child in path
try:
a = Article.objects.get(parent__exact = article, slug__exact=str(path[0]))
return cls.get_url_reverse(path[1:], a, return_list+[article])
except Exception, e:
return None
def can_read(self, user):
""" Check read permissions and return True/False."""
if user.is_superuser:
return True
if self.permissions:
perms = self.permissions.can_read.all()
return perms.count() == 0 or (user in perms)
else:
return self.parent.can_read(user) if self.parent else True
def can_write(self, user):
""" Check write permissions and return True/False."""
if user.is_superuser:
return True
if self.permissions:
perms = self.permissions.can_write.all()
return perms.count() == 0 or (user in perms)
else:
return self.parent.can_write(user) if self.parent else True
def can_write_l(self, user):
"""Check write permissions and locked status"""
if user.is_superuser:
return True
return not self.locked and self.can_write(user)
def can_attach(self, user):
return self.can_write_l(user) and (WIKI_ALLOW_ANON_ATTACHMENTS or not user.is_anonymous())
def __unicode__(self):
if self.slug == '' and not self.parent:
return unicode(_('Root article'))
else:
return self.get_url()
class Meta:
unique_together = (('slug', 'parent'),)
verbose_name = _('Article')
verbose_name_plural = _('Articles')
def get_attachment_filepath(instance, filename):
"""Store file, appending new extension for added security"""
dir_ = WIKI_ATTACHMENTS + instance.article.get_url()
dir_ = '/'.join(filter(lambda x: x!='', dir_.split('/')))
if not os.path.exists(WIKI_ATTACHMENTS_ROOT + dir_):
os.makedirs(WIKI_ATTACHMENTS_ROOT + dir_)
return dir_ + '/' + filename + '.upload'
class ArticleAttachment(models.Model):
article = models.ForeignKey(Article, verbose_name=_('Article'))
file = models.FileField(max_length=255, upload_to=get_attachment_filepath, verbose_name=_('Attachment'))
uploaded_by = models.ForeignKey(User, blank=True, verbose_name=_('Uploaded by'), null=True)
uploaded_on = models.DateTimeField(auto_now_add = True, verbose_name=_('Upload date'))
def download_url(self):
return reverse('wiki_view_attachment', args=(self.article.get_url(), self.filename()))
def filename(self):
return '.'.join(self.file.name.split('/')[-1].split('.')[:-1])
def get_size(self):
try:
size = self.file.size
except OSError:
size = 0
return size
def filename(self):
return '.'.join(self.file.name.split('/')[-1].split('.')[:-1])
def is_image(self):
fname = self.filename().split('.')
if len(fname) > 1 and fname[-1].lower() in WIKI_IMAGE_EXTENSIONS:
return True
return False
def get_thumb(self):
return self.get_thumb_impl(*WIKI_IMAGE_THUMB_SIZE)
def get_thumb_small(self):
return self.get_thumb_impl(*WIKI_IMAGE_THUMB_SIZE_SMALL)
def mk_thumbs(self):
self.mk_thumb(*WIKI_IMAGE_THUMB_SIZE, **{'force':True})
self.mk_thumb(*WIKI_IMAGE_THUMB_SIZE_SMALL, **{'force':True})
def mk_thumb(self, width, height, force=False):
"""Requires Python Imaging Library (PIL)"""
if not self.get_size():
return False
if not self.is_image():
return False
base_path = os.path.dirname(self.file.path)
orig_name = self.filename().split('.')
thumb_filename = "%s__thumb__%d_%d.%s" % ('.'.join(orig_name[:-1]), width, height, orig_name[-1])
thumb_filepath = "%s%s%s" % (base_path, os.sep, thumb_filename)
if force or not os.path.exists(thumb_filepath):
try:
import Image
img = Image.open(self.file.path)
img.thumbnail((width,height), Image.ANTIALIAS)
img.save(thumb_filepath)
except IOError:
return False
return True
def get_thumb_impl(self, width, height):
"""Requires Python Imaging Library (PIL)"""
if not self.get_size():
return False
if not self.is_image():
return False
self.mk_thumb(width, height)
orig_name = self.filename().split('.')
thumb_filename = "%s__thumb__%d_%d.%s" % ('.'.join(orig_name[:-1]), width, height, orig_name[-1])
thumb_url = settings.MEDIA_URL + WIKI_ATTACHMENTS + self.article.get_url() +'/' + thumb_filename
return thumb_url
def __unicode__(self):
return self.filename()
class Revision(models.Model):
article = models.ForeignKey(Article, verbose_name=_('Article'))
revision_text = models.CharField(max_length=255, blank=True, null=True,
verbose_name=_('Description of change'))
revision_user = models.ForeignKey(User, verbose_name=_('Modified by'),
blank=True, null=True, related_name='wiki_revision_user')
revision_date = models.DateTimeField(auto_now_add = True, verbose_name=_('Revision date'))
contents = models.TextField(verbose_name=_('Contents (Use MarkDown format)'))
contents_parsed = models.TextField(editable=False, blank=True, null=True)
counter = models.IntegerField(verbose_name=_('Revision#'), default=1, editable=False)
previous_revision = models.ForeignKey('self', blank=True, null=True, editable=False)
# Deleted has three values. 0 is normal, non-deleted. 1 is if it was deleted by a normal user. It should
# be a NEW revision, so that it appears in the history. 2 is a special flag that can be applied or removed
# from a normal revision. It means it has been admin-deleted, and can only been seen by an admin. It doesn't
# show up in the history.
deleted = models.IntegerField(verbose_name=_('Deleted group'), default=0)
def get_user(self):
return self.revision_user if self.revision_user else _('Anonymous')
# Called after the deleted fied has been changed (between 0 and 2). This bypasses the normal checks put in
# save that update the revision or reject the save if contents haven't changed
def adminSetDeleted(self, deleted):
self.deleted = deleted
super(Revision, self).save()
def save(self, **kwargs):
# Check if contents have changed... if not, silently ignore save
if self.article and self.article.current_revision:
if self.deleted == 0 and self.article.current_revision.contents == self.contents:
return
else:
import datetime
self.article.modified_on = datetime.datetime.now()
self.article.save()
# Increment counter according to previous revision
previous_revision = Revision.objects.filter(article=self.article).order_by('-counter')
if previous_revision.count() > 0:
if previous_revision.count() > previous_revision[0].counter:
self.counter = previous_revision.count() + 1
else:
self.counter = previous_revision[0].counter + 1
else:
self.counter = 1
if (self.article.current_revision and self.article.current_revision.deleted == 0):
self.previous_revision = self.article.current_revision
# Create pre-parsed contents - no need to parse on-the-fly
ext = WIKI_MARKDOWN_EXTENSIONS
ext += ["wikipath(base_url=%s)" % reverse('wiki_view', args=('/',))]
self.contents_parsed = markdown(self.contents,
extensions=ext,
safe_mode='escape',)
super(Revision, self).save(**kwargs)
def delete(self, **kwargs):
"""If a current revision is deleted, then regress to the previous
revision or insert a stub, if no other revisions are available"""
article = self.article
if article.current_revision == self:
prev_revision = Revision.objects.filter(article__exact = article,
pk__not = self.pk).order_by('-counter')
if prev_revision:
article.current_revision = prev_revision[0]
article.save()
else:
r = Revision(article=article,
revision_user = article.created_by)
r.contents = unicode(_('Auto-generated stub'))
r.revision_text= unicode(_('Auto-generated stub'))
r.save()
article.current_revision = r
article.save()
super(Revision, self).delete(**kwargs)
def get_diff(self):
if (self.deleted == 1):
yield "Article Deletion"
return
if self.previous_revision:
previous = self.previous_revision.contents.splitlines(1)
else:
previous = []
# Todo: difflib.HtmlDiff would look pretty for our history pages!
diff = difflib.unified_diff(previous, self.contents.splitlines(1))
# let's skip the preamble
diff.next(); diff.next(); diff.next()
for d in diff:
yield d
def __unicode__(self):
return "r%d" % self.counter
class Meta:
verbose_name = _('article revision')
verbose_name_plural = _('article revisions')
class Permission(models.Model):
permission_name = models.CharField(max_length = 255, verbose_name=_('Permission name'))
can_write = models.ManyToManyField(User, blank=True, null=True, related_name='write',
help_text=_('Select none to grant anonymous access.'))
can_read = models.ManyToManyField(User, blank=True, null=True, related_name='read',
help_text=_('Select none to grant anonymous access.'))
def __unicode__(self):
return self.permission_name
class Meta:
verbose_name = _('Article permission')
verbose_name_plural = _('Article permissions')
class RevisionForm(forms.ModelForm):
contents = forms.CharField(label=_('Contents'), widget=forms.Textarea(attrs={'rows':8, 'cols':50}))
class Meta:
model = Revision
fields = ['contents', 'revision_text']
class RevisionFormWithTitle(forms.ModelForm):
title = forms.CharField(label=_('Title'))
class Meta:
model = Revision
fields = ['title', 'contents', 'revision_text']
class CreateArticleForm(RevisionForm):
title = forms.CharField(label=_('Title'))
class Meta:
model = Revision
fields = ['title', 'contents',]
def set_revision(sender, *args, **kwargs):
"""Signal handler to ensure that a new revision is always chosen as the
current revision - automatically. It simplifies stuff greatly. Also
stores previous revision for diff-purposes"""
instance = kwargs['instance']
created = kwargs['created']
if created and instance.article:
instance.article.current_revision = instance
instance.article.save()
signals.post_save.connect(set_revision, Revision)
......@@ -16,9 +16,23 @@ from util.cache import cache
class ShouldHaveExactlyOneRootSlug(Exception):
pass
class Namespace(models.Model):
name = models.CharField(max_length=30, db_index=True, unique=True, verbose_name=_('namespace'))
# TODO: We may want to add permissions, etc later
@classmethod
def ensure_namespace(cls, name):
try:
namespace = Namespace.objects.get(name__exact = name)
except Namespace.DoesNotExist:
new_namespace = Namespace(name=name)
new_namespace.save()
class Article(models.Model):
"""Wiki article referring to Revision model for actual content.
'slug' and 'parent' field should be maintained centrally, since users
'slug' and 'title' field should be maintained centrally, since users
aren't allowed to change them, anyways.
"""
......@@ -27,12 +41,10 @@ class Article(models.Model):
slug = models.SlugField(max_length=100, verbose_name=_('slug'),
help_text=_('Letters, numbers, underscore and hyphen.'),
blank=True)
namespace = models.ForeignKey(Namespace, verbose_name=_('Namespace'))
created_by = models.ForeignKey(User, verbose_name=_('Created by'), blank=True, null=True)
created_on = models.DateTimeField(auto_now_add = 1)
modified_on = models.DateTimeField(auto_now_add = 1)
parent = models.ForeignKey('self', verbose_name=_('Parent article slug'),
help_text=_('Affects URL structure and possibly inherits permissions'),
null=True, blank=True)
locked = models.BooleanField(default=False, verbose_name=_('Locked for editing'))
permissions = models.ForeignKey('Permission', verbose_name=_('Permissions'),
blank=True, null=True,
......@@ -46,52 +58,43 @@ class Article(models.Model):
def attachments(self):
return ArticleAttachment.objects.filter(article__exact = self)
def get_path(self):
return self.namespace.name + "/" + self.slug
@classmethod
def get_article(cls, article_path):
"""
Given an article_path like namespace/slug, this returns the article. It may raise
a Article.DoesNotExist if no matching article is found or ValueError if the
article_path is not constructed properly.
"""
#TODO: Verify the path, throw a meaningful error?
namespace, slug = article_path.split("/")
return Article.objects.get( slug__exact = slug, namespace__name__exact = namespace)
@classmethod
def get_root(cls):
def get_root(cls, namespace):
"""Return the root article, which should ALWAYS exist..
except the very first time the wiki is loaded, in which
case the user is prompted to create this article."""
try:
return Article.objects.filter(slug__exact = "")[0]
return Article.objects.filter(slug__exact = "", namespace__name__exact = namespace)[0]
except:
raise ShouldHaveExactlyOneRootSlug()
def get_url(self):
"""Return the Wiki URL for an article"""
url = self.slug + "/"
if self.parent_id:
parent_url = cache.get("wiki_url-" + str(self.parent_id))
if parent_url is None:
parent_url = self.parent.get_url()
url = parent_url + url
cache.set("wiki_url-" + str(self.id), url, 60*60)
return url
def get_abs_url(self):
"""Return the absolute path for an article. This is necessary in cases
where the template system isn't used for generating URLs..."""
# TODO: Remove and create a reverse() lookup.
return WIKI_BASE + self.get_url()
@models.permalink
def get_absolute_url(self):
return ('wiki_view', [self.get_url()])
@classmethod
def get_url_reverse(cls, path, article, return_list=[]):
"""Lookup a URL and return the corresponding set of articles
in the path."""
if path == []:
return return_list + [article]
# Lookup next child in path
try:
a = Article.objects.get(parent__exact = article, slug__exact=str(path[0]))
return cls.get_url_reverse(path[1:], a, return_list+[article])
except Exception, e:
return None
# @classmethod
# def get_url_reverse(cls, path, article, return_list=[]):
# """Lookup a URL and return the corresponding set of articles
# in the path."""
# if path == []:
# return return_list + [article]
# # Lookup next child in path
# try:
# a = Article.objects.get(parent__exact = article, slug__exact=str(path[0]))
# return cls.get_url_reverse(path[1:], a, return_list+[article])
# except Exception, e:
# return None
def can_read(self, user):
""" Check read permissions and return True/False."""
......@@ -101,7 +104,8 @@ class Article(models.Model):
perms = self.permissions.can_read.all()
return perms.count() == 0 or (user in perms)
else:
return self.parent.can_read(user) if self.parent else True
# TODO: We can inherit namespace permissions here
return True
def can_write(self, user):
""" Check write permissions and return True/False."""
......@@ -111,7 +115,8 @@ class Article(models.Model):
perms = self.permissions.can_write.all()
return perms.count() == 0 or (user in perms)
else:
return self.parent.can_write(user) if self.parent else True
# TODO: We can inherit namespace permissions here
return True
def can_write_l(self, user):
"""Check write permissions and locked status"""
......@@ -123,13 +128,13 @@ class Article(models.Model):
return self.can_write_l(user) and (WIKI_ALLOW_ANON_ATTACHMENTS or not user.is_anonymous())
def __unicode__(self):
if self.slug == '' and not self.parent:
if self.slug == '':
return unicode(_('Root article'))
else:
return self.get_url()
return self.slug
class Meta:
unique_together = (('slug', 'parent'),)
unique_together = (('slug', 'namespace'),)
verbose_name = _('Article')
verbose_name_plural = _('Articles')
......@@ -275,7 +280,7 @@ class Revision(models.Model):
# Create pre-parsed contents - no need to parse on-the-fly
ext = WIKI_MARKDOWN_EXTENSIONS
ext += ["wikipath(base_url=%s)" % reverse('wiki_view', args=('/',))]
ext += ["wikipath(default_namespace=%s)" % self.article.namespace.name ]
self.contents_parsed = markdown(self.contents,
extensions=ext,
safe_mode='escape',)
......
from django.conf.urls.defaults import *
from django.conf.urls.defaults import patterns, url
namespace_regex = r"[a-zA-Z\d._-]+"
article_slug = r'/(?P<article_path>' + namespace_regex + r'/[a-zA-Z\d_-]*)'
namespace = r'/(?P<namespace>' + namespace_regex + r')'
urlpatterns = patterns('',
url(r'^$', 'simplewiki.views.root_redirect', name='wiki_root'),
url(r'^view(/[a-zA-Z\d/_-]*)/?$', 'simplewiki.views.view', name='wiki_view'),
url(r'^view_revision/([0-9]*)(/[a-zA-Z\d/_-]*)/?$', 'simplewiki.views.view_revision', name='wiki_view_revision'),
url(r'^edit(/[a-zA-Z\d/_-]*)/?$', 'simplewiki.views.edit', name='wiki_edit'),
url(r'^create(/[a-zA-Z\d/_-]*)/?$', 'simplewiki.views.create', name='wiki_create'),
url(r'^history(/[a-zA-Z\d/_-]*)/([0-9]*)/?$', 'simplewiki.views.history', name='wiki_history'),
url(r'^search_related(/[a-zA-Z\d/_-]*)/?$', 'simplewiki.views.search_add_related', name='search_related'),
url(r'^view' + article_slug, 'simplewiki.views.view', name='wiki_view'),
url(r'^view_revision/(?P<revision_number>[0-9]+)' + article_slug, 'simplewiki.views.view_revision', name='wiki_view_revision'),
url(r'^edit' + article_slug, 'simplewiki.views.edit', name='wiki_edit'),
url(r'^create' + article_slug, 'simplewiki.views.create', name='wiki_create'),
url(r'^history' + article_slug + r'(?:/(?P<page>[0-9]+))?$', 'simplewiki.views.history', name='wiki_history'),
url(r'^search_related' + article_slug, 'simplewiki.views.search_add_related', name='search_related'),
url(r'^random/?$', 'simplewiki.views.random_article', name='wiki_random'),
url(r'^revision_feed/([0-9]*)/?$', 'simplewiki.views.revision_feed', name='wiki_revision_feed'),
url(r'^search/?$', 'simplewiki.views.search_articles', name='wiki_search_articles'),
url(r'^list/?$', 'simplewiki.views.search_articles', name='wiki_list_articles'), #Just an alias for the search, but you usually don't submit a search term
# url(r'^/?([a-zA-Z\d/_-]*)/_related/add/$', 'simplewiki.views.add_related', name='add_related'),
# url(r'^/?([a-zA-Z\d/_-]*)/_related/remove/(\d+)$', 'simplewiki.views.remove_related', name='wiki_remove_relation'),
# url(r'^/?([a-zA-Z\d/_-]*)/_add_attachment/$', 'simplewiki.views_attachments.add_attachment', name='add_attachment'),
# url(r'^/?([a-zA-Z\d/_-]*)/_view_attachment/(.+)?$', 'simplewiki.views_attachments.view_attachment', name='wiki_view_attachment'),
# url(r'^(.*)$', 'simplewiki.views.encode_err', name='wiki_encode_err')
url(r'^revision_feed' + namespace + r'/(?P<page>[0-9]+)?$', 'simplewiki.views.revision_feed', name='wiki_revision_feed'),
url(r'^search' + namespace + r'?$', 'simplewiki.views.search_articles', name='wiki_search_articles'),
url(r'^list' + namespace + r'?$', 'simplewiki.views.search_articles', name='wiki_list_articles'), #Just an alias for the search, but you usually don't submit a search term
)
# -*- coding: utf-8 -*-
from django.conf import settings as settings
from django.contrib.auth.decorators import login_required
from django.core.context_processors import csrf
from django.core.urlresolvers import reverse
from django.db.models import Q
from django.http import HttpResponse, HttpResponseRedirect
from django.utils import simplejson
from django.utils.translation import ugettext_lazy as _
from mitxmako.shortcuts import render_to_response
from models import Revision, Article, CreateArticleForm, RevisionFormWithTitle, RevisionForm
import wiki_settings
def view(request, wiki_url):
(article, path, err) = fetch_from_url(request, wiki_url)
if err:
return err
perm_err = check_permissions(request, article, check_read=True, check_deleted=True)
if perm_err:
return perm_err
d = {'wiki_article': article,
'wiki_article_revision':article.current_revision,
'wiki_write': article.can_write_l(request.user),
'wiki_attachments_write': article.can_attach(request.user),
'wiki_current_revision_deleted' : not (article.current_revision.deleted == 0),
'wiki_title' : article.title + " - MITX 6.002x Wiki"
}
d.update(csrf(request))
return render_to_response('simplewiki_view.html', d)
def view_revision(request, revision_number, wiki_url, revision=None):
(article, path, err) = fetch_from_url(request, wiki_url)
if err:
return err
try:
revision = Revision.objects.get(counter=int(revision_number), article=article)
except:
d = {'wiki_article': article,
'wiki_err_norevision': revision_number,}
d.update(csrf(request))
return render_to_response('simplewiki_error.html', d)
perm_err = check_permissions(request, article, check_read=True, check_deleted=True, revision=revision)
if perm_err:
return perm_err
d = {'wiki_article': article,
'wiki_article_revision':revision,
'wiki_write': article.can_write_l(request.user),
'wiki_attachments_write': article.can_attach(request.user),
'wiki_current_revision_deleted' : not (revision.deleted == 0),
}
d.update(csrf(request))
return render_to_response('simplewiki_view.html', d)
def root_redirect(request):
try:
root = Article.get_root()
except:
err = not_found(request, '/')
return err
return HttpResponseRedirect(reverse('wiki_view', args=(root.get_url())))
def create(request, wiki_url):
url_path = get_url_path(wiki_url)
if url_path != [] and url_path[0].startswith('_'):
d = {'wiki_err_keyword': True,
'wiki_url': '/'.join(url_path) }
d.update(csrf(request))
return render_to_response('simplewiki_error.html', d)
# Lookup path
try:
# Ensure that the path exists...
root = Article.get_root()
# Remove root slug if present in path
if url_path and root.slug == url_path[0]:
url_path = url_path[1:]
path = Article.get_url_reverse(url_path[:-1], root)
if not path:
d = {'wiki_err_noparent': True,
'wiki_url_parent': '/'.join(url_path[:-1]) }
d.update(csrf(request))
return render_to_response('simplewiki_error.html', d)
perm_err = check_permissions(request, path[-1], check_locked=False, check_write=True, check_deleted=True)
if perm_err:
return perm_err
# Ensure doesn't already exist
article = Article.get_url_reverse(url_path, root)
if article:
return HttpResponseRedirect(reverse('wiki_view', args=(article[-1].get_url(),)))
# TODO: Somehow this doesnt work...
#except ShouldHaveExactlyOneRootSlug, (e):
except:
if Article.objects.filter(parent=None).count() > 0:
return HttpResponseRedirect(reverse('wiki_view', args=('/',)))
# Root not found...
path = []
url_path = [""]
if request.method == 'POST':
f = CreateArticleForm(request.POST)
if f.is_valid():
article = Article()
article.slug = url_path[-1]
if not request.user.is_anonymous():
article.created_by = request.user
article.title = f.cleaned_data.get('title')
if path != []:
article.parent = path[-1]
a = article.save()
new_revision = f.save(commit=False)
if not request.user.is_anonymous():
new_revision.revision_user = request.user
new_revision.article = article
new_revision.save()
import django.db as db
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
else:
f = CreateArticleForm(initial={'title':request.GET.get('wiki_article_name', url_path[-1]),
'contents':_('Headline\n===\n\n')})
d = {'wiki_form': f,
'wiki_write': True,
'create_article' : True,
}
d.update(csrf(request))
return render_to_response('simplewiki_edit.html', d)
def edit(request, wiki_url):
(article, path, err) = fetch_from_url(request, wiki_url)
if err:
return err
# Check write permissions
perm_err = check_permissions(request, article, check_write=True, check_locked=True, check_deleted=False)
if perm_err:
return perm_err
if wiki_settings.WIKI_ALLOW_TITLE_EDIT:
EditForm = RevisionFormWithTitle
else:
EditForm = RevisionForm
if request.method == 'POST':
f = EditForm(request.POST)
if f.is_valid():
new_revision = f.save(commit=False)
new_revision.article = article
if request.POST.__contains__('delete'):
if (article.current_revision.deleted == 1): #This article has already been deleted. Redirect
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
new_revision.contents = ""
new_revision.deleted = 1
elif not new_revision.get_diff():
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
if not request.user.is_anonymous():
new_revision.revision_user = request.user
new_revision.save()
if wiki_settings.WIKI_ALLOW_TITLE_EDIT:
new_revision.article.title = f.cleaned_data['title']
new_revision.article.save()
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
else:
startContents = article.current_revision.contents if (article.current_revision.deleted == 0) else 'Headline\n===\n\n'
f = EditForm({'contents': startContents, 'title': article.title})
d = {'wiki_form': f,
'wiki_write': True,
'wiki_article': article,
'wiki_title' : article.title,
'wiki_attachments_write': article.can_attach(request.user),
'create_article' : False,
}
d.update(csrf(request))
return render_to_response('simplewiki_edit.html', d)
def history(request, wiki_url, page=1):
(article, path, err) = fetch_from_url(request, wiki_url)
if err:
return err
perm_err = check_permissions(request, article, check_read=True, check_deleted=False)
if perm_err:
print "returned error " , perm_err
return perm_err
page_size = 10
try:
p = int(page)
except ValueError:
p = 1
history = Revision.objects.filter(article__exact = article).order_by('-counter').select_related('previous_revision__counter', 'revision_user', 'wiki_article')
if request.method == 'POST':
if wiki_settings.WIKI_REQUIRE_LOGIN_EDIT and not request.user.is_authenticated():
return HttpResponseRedirect('/')
if request.POST.__contains__('revision'): #They selected a version, but they can be either deleting or changing the version
perm_err = check_permissions(request, article, check_write=True, check_locked=True)
if perm_err:
return perm_err
redirectURL = reverse('wiki_view', args=(article.get_url(),))
try:
r = int(request.POST['revision'])
revision = Revision.objects.get(id=r)
if request.POST.__contains__('change'):
article.current_revision = revision
article.save()
elif request.POST.__contains__('view'):
redirectURL = reverse('wiki_view_revision', args=(revision.counter, article.get_url(),))
#The rese of these are admin functions
elif request.POST.__contains__('delete') and request.user.is_superuser:
if (revision.deleted == 0):
revision.adminSetDeleted(2)
elif request.POST.__contains__('restore') and request.user.is_superuser:
if (revision.deleted == 2):
revision.adminSetDeleted(0)
elif request.POST.__contains__('delete_all') and request.user.is_superuser:
Revision.objects.filter(article__exact = article, deleted = 0).update(deleted = 2)
elif request.POST.__contains__('lock_article'):
print "changing locked article " , article.locked
article.locked = not article.locked
print "changed locked article " , article.locked
article.save()
except:
pass
finally:
return HttpResponseRedirect(redirectURL)
#
#
# <input type="submit" name="delete" value="Delete revision"/>
# <input type="submit" name="restore" value="Restore revision"/>
# <input type="submit" name="delete_all" value="Delete all revisions">
# %else:
# <input type="submit" name="delete_article" value="Delete all revisions">
#
page_count = (history.count()+(page_size-1)) / page_size
if p > page_count:
p = 1
beginItem = (p-1) * page_size
next_page = p + 1 if page_count > p else None
prev_page = p - 1 if p > 1 else None
d = {'wiki_page': p,
'wiki_next_page': next_page,
'wiki_prev_page': prev_page,
'wiki_write': article.can_write_l(request.user),
'wiki_attachments_write': article.can_attach(request.user),
'wiki_article': article,
'wiki_title': article.title,
'wiki_history': history[beginItem:beginItem+page_size],
'show_delete_revision' : request.user.is_superuser,}
d.update(csrf(request))
return render_to_response('simplewiki_history.html', d)
def revision_feed(request, page=1):
page_size = 10
try:
p = int(page)
except ValueError:
p = 1
history = Revision.objects.order_by('-revision_date').select_related('revision_user', 'article', 'previous_revision')
page_count = (history.count()+(page_size-1)) / page_size
if p > page_count:
p = 1
beginItem = (p-1) * page_size
next_page = p + 1 if page_count > p else None
prev_page = p - 1 if p > 1 else None
d = {'wiki_page': p,
'wiki_next_page': next_page,
'wiki_prev_page': prev_page,
'wiki_history': history[beginItem:beginItem+page_size],
'show_delete_revision' : request.user.is_superuser,}
d.update(csrf(request))
return render_to_response('simplewiki_revision_feed.html', d)
def search_articles(request):
# blampe: We should check for the presence of other popular django search
# apps and use those if possible. Only fall back on this as a last resort.
# Adding some context to results (eg where matches were) would also be nice.
# todo: maybe do some perm checking here
if request.method == 'POST':
querystring = request.POST['value'].strip()
else:
querystring = ""
results = Article.objects.all()
if request.user.is_superuser:
results = results.order_by('current_revision__deleted')
else:
results = results.filter(current_revision__deleted = 0)
if querystring:
for queryword in querystring.split():
# Basic negation is as fancy as we get right now
if queryword[0] == '-' and len(queryword) > 1:
results._search = lambda x: results.exclude(x)
queryword = queryword[1:]
else:
results._search = lambda x: results.filter(x)
results = results._search(Q(current_revision__contents__icontains = queryword) | \
Q(title__icontains = queryword))
results = results.select_related('current_revision__deleted')
results = sorted(results, key=lambda article: (article.current_revision.deleted, article.get_url().lower()) )
if len(results) == 1 and querystring:
return HttpResponseRedirect(reverse('wiki_view', args=(results[0].get_url(),)))
else:
d = {'wiki_search_results': results,
'wiki_search_query': querystring,}
d.update(csrf(request))
return render_to_response('simplewiki_searchresults.html', d)
def search_add_related(request, wiki_url):
(article, path, err) = fetch_from_url(request, wiki_url)
if err:
return err
perm_err = check_permissions(request, article, check_read=True)
if perm_err:
return perm_err
search_string = request.GET.get('query', None)
self_pk = request.GET.get('self', None)
if search_string:
results = []
related = Article.objects.filter(title__istartswith = search_string)
others = article.related.all()
if self_pk:
related = related.exclude(pk=self_pk)
if others:
related = related.exclude(related__in = others)
related = related.order_by('title')[:10]
for item in related:
results.append({'id': str(item.id),
'value': item.title,
'info': item.get_url()})
else:
results = []
json = simplejson.dumps({'results': results})
return HttpResponse(json, mimetype='application/json')
def add_related(request, wiki_url):
(article, path, err) = fetch_from_url(request, wiki_url)
if err:
return err
perm_err = check_permissions(request, article, check_write=True, check_locked=True)
if perm_err:
return perm_err
try:
related_id = request.POST['id']
rel = Article.objects.get(id=related_id)
has_already = article.related.filter(id=related_id).count()
if has_already == 0 and not rel == article:
article.related.add(rel)
article.save()
except:
pass
finally:
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
def remove_related(request, wiki_url, related_id):
(article, path, err) = fetch_from_url(request, wiki_url)
if err:
return err
perm_err = check_permissions(request, article, check_write=True, check_locked=True)
if perm_err:
return perm_err
try:
rel_id = int(related_id)
rel = Article.objects.get(id=rel_id)
article.related.remove(rel)
article.save()
except:
pass
finally:
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
def random_article(request):
from random import randint
num_arts = Article.objects.count()
article = Article.objects.all()[randint(0, num_arts-1)]
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
def encode_err(request, url):
d = {'wiki_err_encode': True}
d.update(csrf(request))
return render_to_response('simplewiki_error.html', d)
def not_found(request, wiki_url):
"""Generate a NOT FOUND message for some URL"""
d = {'wiki_err_notfound': True,
'wiki_url': wiki_url}
d.update(csrf(request))
return render_to_response('simplewiki_error.html', d)
def get_url_path(url):
"""Return a list of all actual elements of a url, safely ignoring
double-slashes (//) """
return filter(lambda x: x!='', url.split('/'))
def fetch_from_url(request, url):
"""Analyze URL, returning the article and the articles in its path
If something goes wrong, return an error HTTP response"""
err = None
article = None
path = None
url_path = get_url_path(url)
try:
root = Article.get_root()
except:
err = not_found(request, '/')
return (article, path, err)
if url_path and root.slug == url_path[0]:
url_path = url_path[1:]
path = Article.get_url_reverse(url_path, root)
if not path:
err = not_found(request, '/' + '/'.join(url_path))
else:
article = path[-1]
return (article, path, err)
def check_permissions(request, article, check_read=False, check_write=False, check_locked=False, check_deleted=False, revision = None):
read_err = check_read and not article.can_read(request.user)
write_err = check_write and not article.can_write(request.user)
locked_err = check_locked and article.locked
if revision == None:
revision = article.current_revision
deleted_err = check_deleted and not (revision.deleted == 0)
if (request.user.is_superuser):
deleted_err = False
locked_err = False
if read_err or write_err or locked_err or deleted_err:
d = {'wiki_article': article,
'wiki_err_noread': read_err,
'wiki_err_nowrite': write_err,
'wiki_err_locked': locked_err,
'wiki_err_deleted': deleted_err,}
d.update(csrf(request))
# TODO: Make this a little less jarring by just displaying an error
# on the current page? (no such redirect happens for an anon upload yet)
# benjaoming: I think this is the nicest way of displaying an error, but
# these errors shouldn't occur, but rather be prevented on the other pages.
return render_to_response('simplewiki_error.html', d)
else:
return None
####################
# LOGIN PROTECTION #
####################
if wiki_settings.WIKI_REQUIRE_LOGIN_VIEW:
view = login_required(view)
history = login_required(history)
search_articles = login_required(search_articles)
root_redirect = login_required(root_redirect)
revision_feed = login_required(revision_feed)
random_article = login_required(random_article)
search_add_related = login_required(search_add_related)
not_found = login_required(not_found)
view_revision = login_required(view_revision)
if wiki_settings.WIKI_REQUIRE_LOGIN_EDIT:
create = login_required(create)
edit = login_required(edit)
add_related = login_required(add_related)
remove_related = login_required(remove_related)
if wiki_settings.WIKI_CONTEXT_PREPROCESSORS:
settings.TEMPLATE_CONTEXT_PROCESSORS += wiki_settings.WIKI_CONTEXT_PREPROCESSORS
......@@ -4,155 +4,178 @@ from django.contrib.auth.decorators import login_required
from django.core.context_processors import csrf
from django.core.urlresolvers import reverse
from django.db.models import Q
from django.http import HttpResponse, HttpResponseRedirect
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.utils import simplejson
from django.utils.translation import ugettext_lazy as _
from mitxmako.shortcuts import render_to_response
from multicourse import multicourse_settings
from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.django import modulestore
from models import Revision, Article, CreateArticleForm, RevisionFormWithTitle, RevisionForm
from models import Revision, Article, Namespace, CreateArticleForm, RevisionFormWithTitle, RevisionForm
import wiki_settings
def view(request, wiki_url):
(article, path, err) = fetch_from_url(request, wiki_url)
if err:
return err
def get_course(course_id):
if course_id == None:
return None
course_loc = CourseDescriptor.id_to_location(course_id)
course = modulestore().get_item(course_loc)
# raise Http404("Course not found")
return course
def wiki_reverse(wiki_page, article = None, course = None, namespace=None, args=[], kwargs={}):
kwargs = dict(kwargs) # TODO: Figure out why if I don't do this kwargs sometimes contains {'article_path'}
if not 'course_id' in kwargs and course:
kwargs['course_id'] = course.id
if not 'article_path' in kwargs and article:
kwargs['article_path'] = article.get_path()
if not 'namespace' in kwargs and namespace:
kwargs['namespace'] = namespace
return reverse(wiki_page, kwargs=kwargs, args=args)
def update_template_dictionary(dictionary, request = None, course = None, article = None, revision = None):
if article:
dictionary['wiki_article'] = article
dictionary['wiki_title'] = article.title #TODO: What is the title when viewing the article in a course?
if not course and 'namespace' not in dictionary:
dictionary['namespace'] = article.namespace.name
if course:
dictionary['course'] = course
if 'namespace' not in dictionary:
dictionary['namespace'] = course.wiki_namespace
else:
dictionary['course'] = None
if revision:
dictionary['wiki_article_revision'] = revision
dictionary['wiki_current_revision_deleted'] = not (revision.deleted == 0)
if 'coursename' in request.session: coursename = request.session['coursename']
else: coursename = None
if request:
dictionary.update(csrf(request))
course_number = multicourse_settings.get_course_number(coursename)
perm_err = check_permissions(request, article, check_read=True, check_deleted=True)
def view(request, article_path, course_id=None):
course = get_course(course_id)
(article, err) = get_article(request, article_path, course )
if err:
return err
perm_err = check_permissions(request, article, course, check_read=True, check_deleted=True)
if perm_err:
return perm_err
d = {'wiki_article': article,
'wiki_article_revision':article.current_revision,
'wiki_write': article.can_write_l(request.user),
'wiki_attachments_write': article.can_attach(request.user),
'wiki_current_revision_deleted' : not (article.current_revision.deleted == 0),
'wiki_title' : article.title + " - MITX %s Wiki" % course_number
}
d.update(csrf(request))
return render_to_response('simplewiki_view.html', d)
def view_revision(request, revision_number, wiki_url, revision=None):
(article, path, err) = fetch_from_url(request, wiki_url)
d = {}
update_template_dictionary(d, request, course, article, article.current_revision)
return render_to_response('simplewiki/simplewiki_view.html', d)
def view_revision(request, revision_number, article_path, course_id=None):
course = get_course(course_id)
(article, err) = get_article(request, article_path, course )
if err:
return err
try:
revision = Revision.objects.get(counter=int(revision_number), article=article)
except:
d = {'wiki_article': article,
'wiki_err_norevision': revision_number,}
d.update(csrf(request))
return render_to_response('simplewiki_error.html', d)
d = {'wiki_err_norevision': revision_number}
update_template_dictionary(d, request, course, article)
return render_to_response('simplewiki/simplewiki_error.html', d)
perm_err = check_permissions(request, article, check_read=True, check_deleted=True, revision=revision)
perm_err = check_permissions(request, article, course, check_read=True, check_deleted=True, revision=revision)
if perm_err:
return perm_err
d = {'wiki_article': article,
'wiki_article_revision':revision,
'wiki_write': article.can_write_l(request.user),
'wiki_attachments_write': article.can_attach(request.user),
'wiki_current_revision_deleted' : not (revision.deleted == 0),
}
d.update(csrf(request))
return render_to_response('simplewiki_view.html', d)
d = {}
update_template_dictionary(d, request, course, article, revision)
return render_to_response('simplewiki/simplewiki_view.html', d)
def root_redirect(request):
def root_redirect(request, course_id=None):
course = get_course(course_id)
try:
root = Article.get_root()
root = Article.get_root(course.wiki_namespace)
except:
err = not_found(request, '/')
# If the root is not found, we probably are loading this class for the first time
# We should make sure the namespace exists so the root article can be created.
Namespace.ensure_namespace(course.wiki_namespace)
err = not_found(request, course.wiki_namespace + '/', course)
return err
return HttpResponseRedirect(reverse('wiki_view', args=(root.get_url())))
return HttpResponseRedirect(reverse('wiki_view', kwargs={'course_id' : course_id, 'article_path' : root.get_path()} ))
def create(request, wiki_url):
def create(request, article_path, course_id=None):
course = get_course(course_id)
url_path = get_url_path(wiki_url)
article_path_components = article_path.split('/')
if url_path != [] and url_path[0].startswith('_'):
d = {'wiki_err_keyword': True,
'wiki_url': '/'.join(url_path) }
d.update(csrf(request))
return render_to_response('simplewiki_error.html', d)
# Ensure the namespace exists
if not len(article_path_components) >= 1 or len(article_path_components[0]) == 0:
d = {'wiki_err_no_namespace': True}
update_template_dictionary(d, request, course)
return render_to_response('simplewiki/simplewiki_error.html', d)
# Lookup path
namespace = None
try:
# Ensure that the path exists...
root = Article.get_root()
# Remove root slug if present in path
if url_path and root.slug == url_path[0]:
url_path = url_path[1:]
path = Article.get_url_reverse(url_path[:-1], root)
if not path:
d = {'wiki_err_noparent': True,
'wiki_url_parent': '/'.join(url_path[:-1]) }
d.update(csrf(request))
return render_to_response('simplewiki_error.html', d)
perm_err = check_permissions(request, path[-1], check_locked=False, check_write=True, check_deleted=True)
if perm_err:
return perm_err
# Ensure doesn't already exist
article = Article.get_url_reverse(url_path, root)
if article:
return HttpResponseRedirect(reverse('wiki_view', args=(article[-1].get_url(),)))
namespace = Namespace.objects.get(name__exact = article_path_components[0])
except Namespace.DoesNotExist, ValueError:
d = {'wiki_err_bad_namespace': True}
update_template_dictionary(d, request, course)
return render_to_response('simplewiki/simplewiki_error.html', d)
# TODO: Somehow this doesnt work...
#except ShouldHaveExactlyOneRootSlug, (e):
except:
if Article.objects.filter(parent=None).count() > 0:
return HttpResponseRedirect(reverse('wiki_view', args=('/',)))
# Root not found...
path = []
url_path = [""]
# See if the article already exists
article_slug = article_path_components[1] if len(article_path_components) >= 2 else ''
#TODO: Make sure the slug only contains legal characters (which is already done a bit by the url regex)
try:
existing_article = Article.objects.get(namespace = namespace, slug__exact = article_slug)
#It already exists, so we just redirect to view the article
return HttpResponseRedirect(wiki_reverse("wiki_view", existing_article, course))
except Article.DoesNotExist:
#This is good. The article doesn't exist
pass
#TODO: Once we have permissions for namespaces, we should check for create permissions
#check_permissions(request, #namespace#, check_locked=False, check_write=True, check_deleted=True)
if request.method == 'POST':
f = CreateArticleForm(request.POST)
if f.is_valid():
article = Article()
article.slug = url_path[-1]
article.slug = article_slug
if not request.user.is_anonymous():
article.created_by = request.user
article.title = f.cleaned_data.get('title')
if path != []:
article.parent = path[-1]
article.namespace = namespace
a = article.save()
new_revision = f.save(commit=False)
if not request.user.is_anonymous():
new_revision.revision_user = request.user
new_revision.article = article
new_revision.save()
import django.db as db
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
return HttpResponseRedirect(wiki_reverse("wiki_view", article, course))
else:
f = CreateArticleForm(initial={'title':request.GET.get('wiki_article_name', url_path[-1]),
f = CreateArticleForm(initial={'title':request.GET.get('wiki_article_name', article_slug),
'contents':_('Headline\n===\n\n')})
d = {'wiki_form': f,
'wiki_write': True,
'create_article' : True,
}
d.update(csrf(request))
d = {'wiki_form': f, 'create_article' : True, 'namespace' : namespace.name}
update_template_dictionary(d, request, course)
return render_to_response('simplewiki_edit.html', d)
return render_to_response('simplewiki/simplewiki_edit.html', d)
def edit(request, wiki_url):
(article, path, err) = fetch_from_url(request, wiki_url)
def edit(request, article_path, course_id=None):
course = get_course(course_id)
(article, err) = get_article(request, article_path, course )
if err:
return err
# Check write permissions
perm_err = check_permissions(request, article, check_write=True, check_locked=True, check_deleted=False)
perm_err = check_permissions(request, article, course, check_write=True, check_locked=True, check_deleted=False)
if perm_err:
return perm_err
......@@ -169,11 +192,11 @@ def edit(request, wiki_url):
if request.POST.__contains__('delete'):
if (article.current_revision.deleted == 1): #This article has already been deleted. Redirect
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
return HttpResponseRedirect(wiki_reverse('wiki_view', article, course))
new_revision.contents = ""
new_revision.deleted = 1
elif not new_revision.get_diff():
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
return HttpResponseRedirect(wiki_reverse('wiki_view', article, course))
if not request.user.is_anonymous():
new_revision.revision_user = request.user
......@@ -181,34 +204,30 @@ def edit(request, wiki_url):
if wiki_settings.WIKI_ALLOW_TITLE_EDIT:
new_revision.article.title = f.cleaned_data['title']
new_revision.article.save()
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
return HttpResponseRedirect(wiki_reverse('wiki_view', article, course))
else:
startContents = article.current_revision.contents if (article.current_revision.deleted == 0) else 'Headline\n===\n\n'
f = EditForm({'contents': startContents, 'title': article.title})
d = {'wiki_form': f,
'wiki_write': True,
'wiki_article': article,
'wiki_title' : article.title,
'wiki_attachments_write': article.can_attach(request.user),
'create_article' : False,
}
d.update(csrf(request))
return render_to_response('simplewiki_edit.html', d)
def history(request, wiki_url, page=1):
(article, path, err) = fetch_from_url(request, wiki_url)
d = {'wiki_form': f}
update_template_dictionary(d, request, course, article)
return render_to_response('simplewiki/simplewiki_edit.html', d)
def history(request, article_path, page=1, course_id=None):
course = get_course(course_id)
(article, err) = get_article(request, article_path, course )
if err:
return err
perm_err = check_permissions(request, article, check_read=True, check_deleted=False)
perm_err = check_permissions(request, article, course, check_read=True, check_deleted=False)
if perm_err:
print "returned error " , perm_err
return perm_err
page_size = 10
if page == None:
page = 1
try:
p = int(page)
except ValueError:
......@@ -218,11 +237,11 @@ def history(request, wiki_url, page=1):
if request.method == 'POST':
if request.POST.__contains__('revision'): #They selected a version, but they can be either deleting or changing the version
perm_err = check_permissions(request, article, check_write=True, check_locked=True)
perm_err = check_permissions(request, article, course, check_write=True, check_locked=True)
if perm_err:
return perm_err
redirectURL = reverse('wiki_view', args=(article.get_url(),))
redirectURL = wiki_reverse('wiki_view', article, course)
try:
r = int(request.POST['revision'])
revision = Revision.objects.get(id=r)
......@@ -230,8 +249,8 @@ def history(request, wiki_url, page=1):
article.current_revision = revision
article.save()
elif request.POST.__contains__('view'):
redirectURL = reverse('wiki_view_revision', args=(revision.counter, article.get_url(),))
redirectURL = wiki_reverse('wiki_view_revision', course=course,
kwargs={'revision_number' : revision.counter, 'article_path' : article.get_path()})
#The rese of these are admin functions
elif request.POST.__contains__('delete') and request.user.is_superuser:
if (revision.deleted == 0):
......@@ -242,11 +261,10 @@ def history(request, wiki_url, page=1):
elif request.POST.__contains__('delete_all') and request.user.is_superuser:
Revision.objects.filter(article__exact = article, deleted = 0).update(deleted = 2)
elif request.POST.__contains__('lock_article'):
print "changing locked article " , article.locked
article.locked = not article.locked
print "changed locked article " , article.locked
article.save()
except:
except Exception as e:
print str(e)
pass
finally:
return HttpResponseRedirect(redirectURL)
......@@ -267,23 +285,24 @@ def history(request, wiki_url, page=1):
next_page = p + 1 if page_count > p else None
prev_page = p - 1 if p > 1 else None
d = {'wiki_page': p,
'wiki_next_page': next_page,
'wiki_prev_page': prev_page,
'wiki_write': article.can_write_l(request.user),
'wiki_attachments_write': article.can_attach(request.user),
'wiki_article': article,
'wiki_title': article.title,
'wiki_history': history[beginItem:beginItem+page_size],
'show_delete_revision' : request.user.is_superuser,}
d.update(csrf(request))
'show_delete_revision' : request.user.is_superuser}
update_template_dictionary(d, request, course, article)
return render_to_response('simplewiki/simplewiki_history.html', d)
return render_to_response('simplewiki_history.html', d)
def revision_feed(request, page=1, namespace=None, course_id=None):
course = get_course(course_id)
def revision_feed(request, page=1):
page_size = 10
if page == None:
page = 1
try:
p = int(page)
except ValueError:
......@@ -303,25 +322,29 @@ def revision_feed(request, page=1):
'wiki_next_page': next_page,
'wiki_prev_page': prev_page,
'wiki_history': history[beginItem:beginItem+page_size],
'show_delete_revision' : request.user.is_superuser,}
d.update(csrf(request))
'show_delete_revision' : request.user.is_superuser,
'namespace' : namespace}
update_template_dictionary(d, request, course)
return render_to_response('simplewiki_revision_feed.html', d)
return render_to_response('simplewiki/simplewiki_revision_feed.html', d)
def search_articles(request):
def search_articles(request, namespace=None, course_id = None):
# blampe: We should check for the presence of other popular django search
# apps and use those if possible. Only fall back on this as a last resort.
# Adding some context to results (eg where matches were) would also be nice.
# todo: maybe do some perm checking here
if request.method == 'POST':
querystring = request.POST['value'].strip()
if request.method == 'GET':
querystring = request.GET.get('value', '').strip()
else:
querystring = ""
course = get_course(course_id)
results = Article.objects.all()
if namespace:
results = results.filter(namespace__name__exact = namespace)
if request.user.is_superuser:
results = results.order_by('current_revision__deleted')
......@@ -341,25 +364,26 @@ def search_articles(request):
results = results._search(Q(current_revision__contents__icontains = queryword) | \
Q(title__icontains = queryword))
results = results.select_related('current_revision__deleted')
results = results.select_related('current_revision__deleted','namespace')
results = sorted(results, key=lambda article: (article.current_revision.deleted, article.get_url().lower()) )
results = sorted(results, key=lambda article: (article.current_revision.deleted, article.get_path().lower()) )
if len(results) == 1 and querystring:
return HttpResponseRedirect(reverse('wiki_view', args=(results[0].get_url(),)))
return HttpResponseRedirect(wiki_reverse('wiki_view', article=results[0], course=course ))
else:
d = {'wiki_search_results': results,
'wiki_search_query': querystring,}
d.update(csrf(request))
return render_to_response('simplewiki_searchresults.html', d)
'wiki_search_query': querystring,
'namespace' : namespace}
update_template_dictionary(d, request, course)
return render_to_response('simplewiki/simplewiki_searchresults.html', d)
def search_add_related(request, wiki_url):
(article, path, err) = fetch_from_url(request, wiki_url)
def search_add_related(request, course_id, slug, namespace):
(article, err) = get_article(request, slug, namespace if namespace else course_id )
if err:
return err
perm_err = check_permissions(request, article, check_read=True)
perm_err = check_permissions(request, article, course, check_read=True)
if perm_err:
return perm_err
......@@ -384,13 +408,12 @@ def search_add_related(request, wiki_url):
json = simplejson.dumps({'results': results})
return HttpResponse(json, mimetype='application/json')
def add_related(request, wiki_url):
(article, path, err) = fetch_from_url(request, wiki_url)
def add_related(request, course_id, slug, namespace):
(article, err) = get_article(request, slug, namespace if namespace else course_id )
if err:
return err
perm_err = check_permissions(request, article, check_write=True, check_locked=True)
perm_err = check_permissions(request, article, course, check_write=True, check_locked=True)
if perm_err:
return perm_err
......@@ -406,12 +429,13 @@ def add_related(request, wiki_url):
finally:
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
def remove_related(request, wiki_url, related_id):
(article, path, err) = fetch_from_url(request, wiki_url)
def remove_related(request, course_id, namespace, slug, related_id):
(article, err) = get_article(request, slug, namespace if namespace else course_id )
if err:
return err
perm_err = check_permissions(request, article, check_write=True, check_locked=True)
perm_err = check_permissions(request, article, course, check_write=True, check_locked=True)
if perm_err:
return perm_err
......@@ -425,57 +449,33 @@ def remove_related(request, wiki_url, related_id):
finally:
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
def random_article(request):
def random_article(request, course_id):
course = get_course(course_id)
from random import randint
num_arts = Article.objects.count()
article = Article.objects.all()[randint(0, num_arts-1)]
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
return HttpResponseRedirect( wiki_reverse('wiki_view', article, course))
def encode_err(request, url):
d = {'wiki_err_encode': True}
d.update(csrf(request))
return render_to_response('simplewiki_error.html', d)
def not_found(request, wiki_url):
def not_found(request, article_path, course):
"""Generate a NOT FOUND message for some URL"""
d = {'wiki_err_notfound': True,
'wiki_url': wiki_url}
d.update(csrf(request))
return render_to_response('simplewiki_error.html', d)
def get_url_path(url):
"""Return a list of all actual elements of a url, safely ignoring
double-slashes (//) """
return filter(lambda x: x!='', url.split('/'))
def fetch_from_url(request, url):
"""Analyze URL, returning the article and the articles in its path
If something goes wrong, return an error HTTP response"""
'article_path': article_path,
'namespace' : course.wiki_namespace}
update_template_dictionary(d, request, course)
return render_to_response('simplewiki/simplewiki_error.html', d)
def get_article(request, article_path, course):
err = None
article = None
path = None
url_path = get_url_path(url)
try:
root = Article.get_root()
except:
err = not_found(request, '/')
return (article, path, err)
if url_path and root.slug == url_path[0]:
url_path = url_path[1:]
path = Article.get_url_reverse(url_path, root)
if not path:
err = not_found(request, '/' + '/'.join(url_path))
else:
article = path[-1]
return (article, path, err)
article = Article.get_article(article_path)
except Article.DoesNotExist, ValueError:
err = not_found(request, article_path, course)
return (article, err)
def check_permissions(request, article, check_read=False, check_write=False, check_locked=False, check_deleted=False, revision = None):
def check_permissions(request, article, course, check_read=False, check_write=False, check_locked=False, check_deleted=False, revision = None):
read_err = check_read and not article.can_read(request.user)
write_err = check_write and not article.can_write(request.user)
......@@ -495,12 +495,12 @@ def check_permissions(request, article, check_read=False, check_write=False, che
'wiki_err_nowrite': write_err,
'wiki_err_locked': locked_err,
'wiki_err_deleted': deleted_err,}
d.update(csrf(request))
update_template_dictionary(d, request, course)
# TODO: Make this a little less jarring by just displaying an error
# on the current page? (no such redirect happens for an anon upload yet)
# benjaoming: I think this is the nicest way of displaying an error, but
# these errors shouldn't occur, but rather be prevented on the other pages.
return render_to_response('simplewiki_error.html', d)
return render_to_response('simplewiki/simplewiki_error.html', d)
else:
return None
......@@ -508,6 +508,7 @@ def check_permissions(request, article, check_read=False, check_write=False, che
# LOGIN PROTECTION #
####################
if wiki_settings.WIKI_REQUIRE_LOGIN_VIEW:
view = login_required(view)
history = login_required(history)
......
/**
* author: Timothy Groves - http://www.brandspankingnew.net
* version: 1.2 - 2006-11-17
* 1.3 - 2006-12-04
* 2.0 - 2007-02-07
*
*/
var useBSNns;
if (useBSNns)
{
if (typeof(bsn) == "undefined")
bsn = {}
_bsn = bsn;
}
else
{
_bsn = this;
}
if (typeof(_bsn.Autosuggest) == "undefined")
_bsn.Autosuggest = {}
_bsn.AutoSuggest = function (fldID, param)
{
// no DOM - give up!
//
if (!document.getElementById)
return false;
// get field via DOM
//
this.fld = _bsn.DOM.getElement(fldID);
if (!this.fld)
return false;
// init variables
//
this.sInput = "";
this.nInputChars = 0;
this.aSuggestions = [];
this.iHighlighted = 0;
// parameters object
//
this.oP = (param) ? param : {};
// defaults
//
if (!this.oP.minchars) this.oP.minchars = 1;
if (!this.oP.method) this.oP.meth = "get";
if (!this.oP.varname) this.oP.varname = "input";
if (!this.oP.className) this.oP.className = "autosuggest";
if (!this.oP.timeout) this.oP.timeout = 2500;
if (!this.oP.delay) this.oP.delay = 500;
if (!this.oP.offsety) this.oP.offsety = -5;
if (!this.oP.shownoresults) this.oP.shownoresults = true;
if (!this.oP.noresults) this.oP.noresults = "No results!";
if (!this.oP.maxheight && this.oP.maxheight !== 0) this.oP.maxheight = 250;
if (!this.oP.cache && this.oP.cache != false) this.oP.cache = true;
// set keyup handler for field
// and prevent autocomplete from client
//
var pointer = this;
// NOTE: not using addEventListener because UpArrow fired twice in Safari
//_bsn.DOM.addEvent( this.fld, 'keyup', function(ev){ return pointer.onKeyPress(ev); } );
this.fld.onkeypress = function(ev){ return pointer.onKeyPress(ev); }
this.fld.onkeyup = function(ev){ return pointer.onKeyUp(ev); }
this.fld.setAttribute("autocomplete","off");
}
_bsn.AutoSuggest.prototype.onKeyPress = function(ev)
{
var key = (window.event) ? window.event.keyCode : ev.keyCode;
// set responses to keydown events in the field
// this allows the user to use the arrow keys to scroll through the results
// ESCAPE clears the list
// TAB sets the current highlighted value
//
var RETURN = 13;
var TAB = 9;
var ESC = 27;
var bubble = true;
switch(key)
{
case RETURN:
this.setHighlightedValue();
bubble = false;
break;
case ESC:
this.clearSuggestions();
break;
}
return bubble;
}
_bsn.AutoSuggest.prototype.onKeyUp = function(ev)
{
var key = (window.event) ? window.event.keyCode : ev.keyCode;
// set responses to keydown events in the field
// this allows the user to use the arrow keys to scroll through the results
// ESCAPE clears the list
// TAB sets the current highlighted value
//
var ARRUP = 38;
var ARRDN = 40;
var bubble = true;
switch(key)
{
case ARRUP:
this.changeHighlight(key);
bubble = false;
break;
case ARRDN:
this.changeHighlight(key);
bubble = false;
break;
default:
this.getSuggestions(this.fld.value);
}
return bubble;
}
_bsn.AutoSuggest.prototype.getSuggestions = function (val)
{
// if input stays the same, do nothing
//
if (val == this.sInput)
return false;
// input length is less than the min required to trigger a request
// reset input string
// do nothing
//
if (val.length < this.oP.minchars)
{
this.sInput = "";
return false;
}
// if caching enabled, and user is typing (ie. length of input is increasing)
// filter results out of aSuggestions from last request
//
if (val.length>this.nInputChars && this.aSuggestions.length && this.oP.cache)
{
var arr = [];
for (var i=0;i<this.aSuggestions.length;i++)
{
if (this.aSuggestions[i].value.substr(0,val.length).toLowerCase() == val.toLowerCase())
arr.push( this.aSuggestions[i] );
}
this.sInput = val;
this.nInputChars = val.length;
this.aSuggestions = arr;
this.createList(this.aSuggestions);
return false;
}
else
// do new request
//
{
this.sInput = val;
this.nInputChars = val.length;
var pointer = this;
clearTimeout(this.ajID);
this.ajID = setTimeout( function() { pointer.doAjaxRequest() }, this.oP.delay );
}
return false;
}
_bsn.AutoSuggest.prototype.doAjaxRequest = function ()
{
var pointer = this;
// create ajax request
var url = this.oP.script+this.oP.varname+"="+escape(this.fld.value);
var meth = this.oP.meth;
var onSuccessFunc = function (req) { pointer.setSuggestions(req) };
var onErrorFunc = function (status) { alert("AJAX error: "+status); };
var myAjax = new _bsn.Ajax();
myAjax.makeRequest( url, meth, onSuccessFunc, onErrorFunc );
}
_bsn.AutoSuggest.prototype.setSuggestions = function (req)
{
this.aSuggestions = [];
if (this.oP.json)
{
var jsondata = eval('(' + req.responseText + ')');
for (var i=0;i<jsondata.results.length;i++)
{
this.aSuggestions.push( { 'id':jsondata.results[i].id, 'value':jsondata.results[i].value, 'info':jsondata.results[i].info } );
}
}
else
{
var xml = req.responseXML;
// traverse xml
//
var results = xml.getElementsByTagName('results')[0].childNodes;
for (var i=0;i<results.length;i++)
{
if (results[i].hasChildNodes())
this.aSuggestions.push( { 'id':results[i].getAttribute('id'), 'value':results[i].childNodes[0].nodeValue, 'info':results[i].getAttribute('info') } );
}
}
this.idAs = "as_"+this.fld.id;
this.createList(this.aSuggestions);
}
_bsn.AutoSuggest.prototype.createList = function(arr)
{
var pointer = this;
// get rid of old list
// and clear the list removal timeout
//
_bsn.DOM.removeElement(this.idAs);
this.killTimeout();
// create holding div
//
var div = _bsn.DOM.createElement("div", {id:this.idAs, className:this.oP.className});
var hcorner = _bsn.DOM.createElement("div", {className:"as_corner"});
var hbar = _bsn.DOM.createElement("div", {className:"as_bar"});
var header = _bsn.DOM.createElement("div", {className:"as_header"});
header.appendChild(hcorner);
header.appendChild(hbar);
div.appendChild(header);
// create and populate ul
//
var ul = _bsn.DOM.createElement("ul", {id:"as_ul"});
// loop throught arr of suggestions
// creating an LI element for each suggestion
//
for (var i=0;i<arr.length;i++)
{
// format output with the input enclosed in a EM element
// (as HTML, not DOM)
//
var val = arr[i].value;
var st = val.toLowerCase().indexOf( this.sInput.toLowerCase() );
var output = val.substring(0,st) + "<em>" + val.substring(st, st+this.sInput.length) + "</em>" + val.substring(st+this.sInput.length);
var span = _bsn.DOM.createElement("span", {}, output, true);
if (arr[i].info != "")
{
var br = _bsn.DOM.createElement("br", {});
span.appendChild(br);
var small = _bsn.DOM.createElement("small", {}, arr[i].info);
span.appendChild(small);
}
var a = _bsn.DOM.createElement("a", { href:"#" });
var tl = _bsn.DOM.createElement("span", {className:"tl"}, " ");
var tr = _bsn.DOM.createElement("span", {className:"tr"}, " ");
a.appendChild(tl);
a.appendChild(tr);
a.appendChild(span);
a.name = i+1;
a.onclick = function () { pointer.setHighlightedValue(); return false; }
a.onmouseover = function () { pointer.setHighlight(this.name); }
var li = _bsn.DOM.createElement( "li", {}, a );
ul.appendChild( li );
}
// no results
//
if (arr.length == 0)
{
var li = _bsn.DOM.createElement( "li", {className:"as_warning"}, this.oP.noresults );
ul.appendChild( li );
}
div.appendChild( ul );
var fcorner = _bsn.DOM.createElement("div", {className:"as_corner"});
var fbar = _bsn.DOM.createElement("div", {className:"as_bar"});
var footer = _bsn.DOM.createElement("div", {className:"as_footer"});
footer.appendChild(fcorner);
footer.appendChild(fbar);
div.appendChild(footer);
// get position of target textfield
// position holding div below it
// set width of holding div to width of field
//
var pos = _bsn.DOM.getPos(this.fld);
div.style.left = pos.x + "px";
div.style.top = ( pos.y + this.fld.offsetHeight + this.oP.offsety ) + "px";
div.style.width = this.fld.offsetWidth + "px";
// set mouseover functions for div
// when mouse pointer leaves div, set a timeout to remove the list after an interval
// when mouse enters div, kill the timeout so the list won't be removed
//
div.onmouseover = function(){ pointer.killTimeout() }
div.onmouseout = function(){ pointer.resetTimeout() }
// add DIV to document
//
document.getElementsByTagName("body")[0].appendChild(div);
// currently no item is highlighted
//
this.iHighlighted = 0;
// remove list after an interval
//
var pointer = this;
this.toID = setTimeout(function () { pointer.clearSuggestions() }, this.oP.timeout);
}
_bsn.AutoSuggest.prototype.changeHighlight = function(key)
{
var list = _bsn.DOM.getElement("as_ul");
if (!list)
return false;
var n;
if (key == 40)
n = this.iHighlighted + 1;
else if (key == 38)
n = this.iHighlighted - 1;
if (n > list.childNodes.length)
n = list.childNodes.length;
if (n < 1)
n = 1;
this.setHighlight(n);
}
_bsn.AutoSuggest.prototype.setHighlight = function(n)
{
var list = _bsn.DOM.getElement("as_ul");
if (!list)
return false;
if (this.iHighlighted > 0)
this.clearHighlight();
this.iHighlighted = Number(n);
list.childNodes[this.iHighlighted-1].className = "as_highlight";
this.killTimeout();
}
_bsn.AutoSuggest.prototype.clearHighlight = function()
{
var list = _bsn.DOM.getElement("as_ul");
if (!list)
return false;
if (this.iHighlighted > 0)
{
list.childNodes[this.iHighlighted-1].className = "";
this.iHighlighted = 0;
}
}
_bsn.AutoSuggest.prototype.setHighlightedValue = function ()
{
if (this.iHighlighted)
{
this.sInput = this.fld.value = this.aSuggestions[ this.iHighlighted-1 ].value;
// move cursor to end of input (safari)
//
this.fld.focus();
if (this.fld.selectionStart)
this.fld.setSelectionRange(this.sInput.length, this.sInput.length);
this.clearSuggestions();
// pass selected object to callback function, if exists
//
if (typeof(this.oP.callback) == "function")
this.oP.callback( this.aSuggestions[this.iHighlighted-1] );
}
}
_bsn.AutoSuggest.prototype.killTimeout = function()
{
clearTimeout(this.toID);
}
_bsn.AutoSuggest.prototype.resetTimeout = function()
{
clearTimeout(this.toID);
var pointer = this;
this.toID = setTimeout(function () { pointer.clearSuggestions() }, 1000);
}
_bsn.AutoSuggest.prototype.clearSuggestions = function ()
{
this.killTimeout();
var ele = _bsn.DOM.getElement(this.idAs);
var pointer = this;
if (ele)
{
var fade = new _bsn.Fader(ele,1,0,250,function () { _bsn.DOM.removeElement(pointer.idAs) });
}
}
// AJAX PROTOTYPE _____________________________________________
if (typeof(_bsn.Ajax) == "undefined")
_bsn.Ajax = {}
_bsn.Ajax = function ()
{
this.req = {};
this.isIE = false;
}
_bsn.Ajax.prototype.makeRequest = function (url, meth, onComp, onErr)
{
if (meth != "POST")
meth = "GET";
this.onComplete = onComp;
this.onError = onErr;
var pointer = this;
// branch for native XMLHttpRequest object
if (window.XMLHttpRequest)
{
this.req = new XMLHttpRequest();
this.req.onreadystatechange = function () { pointer.processReqChange() };
this.req.open("GET", url, true); //
this.req.send(null);
// branch for IE/Windows ActiveX version
}
else if (window.ActiveXObject)
{
this.req = new ActiveXObject("Microsoft.XMLHTTP");
if (this.req)
{
this.req.onreadystatechange = function () { pointer.processReqChange() };
this.req.open(meth, url, true);
this.req.send();
}
}
}
_bsn.Ajax.prototype.processReqChange = function()
{
// only if req shows "loaded"
if (this.req.readyState == 4) {
// only if "OK"
if (this.req.status == 200)
{
this.onComplete( this.req );
} else {
this.onError( this.req.status );
}
}
}
// DOM PROTOTYPE _____________________________________________
if (typeof(_bsn.DOM) == "undefined")
_bsn.DOM = {}
_bsn.DOM.createElement = function ( type, attr, cont, html )
{
var ne = document.createElement( type );
if (!ne)
return false;
for (var a in attr)
ne[a] = attr[a];
if (typeof(cont) == "string" && !html)
ne.appendChild( document.createTextNode(cont) );
else if (typeof(cont) == "string" && html)
ne.innerHTML = cont;
else if (typeof(cont) == "object")
ne.appendChild( cont );
return ne;
}
_bsn.DOM.clearElement = function ( id )
{
var ele = this.getElement( id );
if (!ele)
return false;
while (ele.childNodes.length)
ele.removeChild( ele.childNodes[0] );
return true;
}
_bsn.DOM.removeElement = function ( ele )
{
var e = this.getElement(ele);
if (!e)
return false;
else if (e.parentNode.removeChild(e))
return true;
else
return false;
}
_bsn.DOM.replaceContent = function ( id, cont, html )
{
var ele = this.getElement( id );
if (!ele)
return false;
this.clearElement( ele );
if (typeof(cont) == "string" && !html)
ele.appendChild( document.createTextNode(cont) );
else if (typeof(cont) == "string" && html)
ele.innerHTML = cont;
else if (typeof(cont) == "object")
ele.appendChild( cont );
}
_bsn.DOM.getElement = function ( ele )
{
if (typeof(ele) == "undefined")
{
return false;
}
else if (typeof(ele) == "string")
{
var re = document.getElementById( ele );
if (!re)
return false;
else if (typeof(re.appendChild) != "undefined" ) {
return re;
} else {
return false;
}
}
else if (typeof(ele.appendChild) != "undefined")
return ele;
else
return false;
}
_bsn.DOM.appendChildren = function ( id, arr )
{
var ele = this.getElement( id );
if (!ele)
return false;
if (typeof(arr) != "object")
return false;
for (var i=0;i<arr.length;i++)
{
var cont = arr[i];
if (typeof(cont) == "string")
ele.appendChild( document.createTextNode(cont) );
else if (typeof(cont) == "object")
ele.appendChild( cont );
}
}
_bsn.DOM.getPos = function ( ele )
{
var ele = this.getElement(ele);
var obj = ele;
var curleft = 0;
if (obj.offsetParent)
{
while (obj.offsetParent)
{
curleft += obj.offsetLeft
obj = obj.offsetParent;
}
}
else if (obj.x)
curleft += obj.x;
var obj = ele;
var curtop = 0;
if (obj.offsetParent)
{
while (obj.offsetParent)
{
curtop += obj.offsetTop
obj = obj.offsetParent;
}
}
else if (obj.y)
curtop += obj.y;
return {x:curleft, y:curtop}
}
// FADER PROTOTYPE _____________________________________________
if (typeof(_bsn.Fader) == "undefined")
_bsn.Fader = {}
_bsn.Fader = function (ele, from, to, fadetime, callback)
{
if (!ele)
return false;
this.ele = ele;
this.from = from;
this.to = to;
this.callback = callback;
this.nDur = fadetime;
this.nInt = 50;
this.nTime = 0;
var p = this;
this.nID = setInterval(function() { p._fade() }, this.nInt);
}
_bsn.Fader.prototype._fade = function()
{
this.nTime += this.nInt;
var ieop = Math.round( this._tween(this.nTime, this.from, this.to, this.nDur) * 100 );
var op = ieop / 100;
if (this.ele.filters) // internet explorer
{
try
{
this.ele.filters.item("DXImageTransform.Microsoft.Alpha").opacity = ieop;
} catch (e) {
// If it is not set initially, the browser will throw an error. This will set it if it is not set yet.
this.ele.style.filter = 'progid:DXImageTransform.Microsoft.Alpha(opacity='+ieop+')';
}
}
else // other browsers
{
this.ele.style.opacity = op;
}
if (this.nTime == this.nDur)
{
clearInterval( this.nID );
if (this.callback != undefined)
this.callback();
}
}
_bsn.Fader.prototype._tween = function(t,b,c,d)
{
return b + ( (c-b) * (t/d) );
}
\ No newline at end of file
.CodeMirror {
line-height: 1em;
font-family: monospace;
}
.CodeMirror-scroll {
overflow: auto;
height: 300px;
/* This is needed to prevent an IE[67] bug where the scrolled content
is visible outside of the scrolling box. */
position: relative;
outline: none;
}
.CodeMirror-gutter {
position: absolute; left: 0; top: 0;
z-index: 10;
background-color: #f7f7f7;
border-right: 1px solid #eee;
min-width: 2em;
height: 100%;
}
.CodeMirror-gutter-text {
color: #aaa;
text-align: right;
padding: .4em .2em .4em .4em;
white-space: pre !important;
}
.CodeMirror-lines {
padding: .4em;
white-space: pre;
}
.CodeMirror pre {
-moz-border-radius: 0;
-webkit-border-radius: 0;
-o-border-radius: 0;
border-radius: 0;
border-width: 0; margin: 0; padding: 0; background: transparent;
font-family: inherit;
font-size: inherit;
padding: 0; margin: 0;
white-space: pre;
word-wrap: normal;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
}
.CodeMirror-wrap .CodeMirror-scroll {
overflow-x: hidden;
}
.CodeMirror textarea {
outline: none !important;
}
.CodeMirror pre.CodeMirror-cursor {
z-index: 10;
position: absolute;
visibility: hidden;
border-left: 1px solid black;
border-right:none;
width:0;
}
.CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {}
.CodeMirror-focused pre.CodeMirror-cursor {
visibility: visible;
}
div.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-searching {
background: #ffa;
background: rgba(255, 255, 0, .4);
}
/* Default theme */
.cm-s-default span.cm-keyword {color: #708;}
.cm-s-default span.cm-atom {color: #219;}
.cm-s-default span.cm-number {color: #164;}
.cm-s-default span.cm-def {color: #00f;}
.cm-s-default span.cm-variable {color: black;}
.cm-s-default span.cm-variable-2 {color: #05a;}
.cm-s-default span.cm-variable-3 {color: #085;}
.cm-s-default span.cm-property {color: black;}
.cm-s-default span.cm-operator {color: black;}
.cm-s-default span.cm-comment {color: #a50;}
.cm-s-default span.cm-string {color: #a11;}
.cm-s-default span.cm-string-2 {color: #f50;}
.cm-s-default span.cm-meta {color: #555;}
.cm-s-default span.cm-error {color: #f00;}
.cm-s-default span.cm-qualifier {color: #555;}
.cm-s-default span.cm-builtin {color: #30a;}
.cm-s-default span.cm-bracket {color: #cc7;}
.cm-s-default span.cm-tag {color: #170;}
.cm-s-default span.cm-attribute {color: #00c;}
.cm-s-default span.cm-header {color: #a0a;}
.cm-s-default span.cm-quote {color: #090;}
.cm-s-default span.cm-hr {color: #999;}
.cm-s-default span.cm-link {color: #00c;}
span.cm-header, span.cm-strong {font-weight: bold;}
span.cm-em {font-style: italic;}
span.cm-emstrong {font-style: italic; font-weight: bold;}
span.cm-link {text-decoration: underline;}
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
This source diff could not be displayed because it is too large. You can view the blob instead.
var schematic_height = 480;
var schematic_width = 640;
$(function(){
$(document).ready(function() {
//$("a[rel*=leanModal]").leanModal(); //TODO: Make this work with the new modal library. Try and integrate this with the "slices"
$("body").append('<div id="circuit_editor" class="leanModal_box" style="z-index: 11000; left: 50%; margin-left: -250px; position: absolute; top: 100px; opacity: 1; "><div align="center">'+
'<input class="schematic" height="' + schematic_height + '" width="' + schematic_width + '" id="schematic_editor" name="schematic" type="hidden" value=""/>' +
'<button type="button" id="circuit_save_btn">save</button></div></div>');
//This is the editor that pops up as a modal
var editorCircuit = $("#schematic_editor").get(0);
//This is the circuit that they last clicked. The one being edited.
var editingCircuit = null;
//Notice we use live, because new circuits can be inserted
$(".schematic_open").live("click", function() {
//Find the new editingCircuit. Transfer its contents to the editorCircuit
editingCircuit = $(this).children("input.schematic").get(0);
editingCircuit.schematic.update_value();
var circuit_so_far = $(editingCircuit).val();
var n = editorCircuit.schematic.components.length;
for (var i = 0; i < n; i++)
editorCircuit.schematic.components[n - 1 - i].remove();
editorCircuit.schematic.load_schematic(circuit_so_far, "");
});
$("#circuit_save_btn").click(function () {
//Take the circuit from the editor and put it back into editingCircuit
editorCircuit.schematic.update_value();
var saving_circuit = $(editorCircuit).val();
var n = editingCircuit.schematic.components.length;
for (var i = 0; i < n; i++)
editingCircuit.schematic.components[n - 1 - i].remove();
editingCircuit.schematic.load_schematic(saving_circuit, "");
if (editingCircuit.codeMirrorLine) {
editingCircuit.codeMirrorLine.replace(0, null, "circuit-schematic:" + saving_circuit);
}
$(".modal_close").first().click();
});
});
});
CodeMirror.defineMode("mitx_markdown", function(cmCfg, modeCfg) {
var htmlMode = CodeMirror.getMode(cmCfg, { name: 'xml', htmlMode: true });
var header = 'header'
, code = 'comment'
, quote = 'quote'
, list = 'string'
, hr = 'hr'
, linktext = 'link'
, linkhref = 'string'
, em = 'em'
, strong = 'strong'
, emstrong = 'emstrong';
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
var circuit_formatter = {
creator: function(text) {
var circuit_value = text.match(circuitRE)[1]
circuit_value = escapeHtml(circuit_value);
var html = "<div style='display:block;line-height:0;' class='schematic_container'><a href='#circuit_editor' rel='leanModal' class='schematic_open' style='display:inline-block;'>" +
"<input type='hidden' parts='' value='" + circuit_value + "' width='" + schematic_width + "' height='" + schematic_height + "' analyses='' class='schematic ctrls'/></a></div>";
return html;
},
size: function(text) {
return {width: schematic_width, height:schematic_height};
},
callback: function(node, line) {
try {
update_schematics();
var schmInput = node.firstChild.firstChild;
schmInput.codeMirrorLine = line;
if (schmInput.schematic) { //This is undefined if there was an error making the schematic
schmInput.schematic.canvas.style.display = "block"; //Otherwise, it gets line height and is a weird size
schmInput.schematic.always_draw_grid = true;
schmInput.schematic.redraw_background();
}
$(node.firstChild).leanModal();
} catch (err) {
console.log("Error in mitx_markdown callback: " + err);
}
}
};
var hrRE = /^[*-=_]/
, ulRE = /^[*-+]\s+/
, olRE = /^[0-9]+\.\s+/
, headerRE = /^(?:\={3,}|-{3,})$/
, codeRE = /^(k:\t|\s{4,})/
, textRE = /^[^\[*_\\<>`]+/
, circuitRE = /^circuit-schematic:(.*)$/;
function switchInline(stream, state, f) {
state.f = state.inline = f;
return f(stream, state);
}
function switchBlock(stream, state, f) {
state.f = state.block = f;
return f(stream, state);
}
// Blocks
function blockNormal(stream, state) {
var match;
if (stream.match(circuitRE)) {
stream.skipToEnd();
return circuit_formatter;
} else if (stream.match(codeRE)) {
stream.skipToEnd();
return code;
} else if (stream.eatSpace()) {
return null;
} else if (stream.peek() === '#' || stream.match(headerRE)) {
state.header = true;
} else if (stream.eat('>')) {
state.indentation++;
state.quote = true;
} else if (stream.peek() === '[') {
return switchInline(stream, state, footnoteLink);
} else if (hrRE.test(stream.peek())) {
var re = new RegExp('(?:\s*['+stream.peek()+']){3,}$');
if (stream.match(re, true)) {
return hr;
}
} else if (match = stream.match(ulRE, true) || stream.match(olRE, true)) {
state.indentation += match[0].length;
return list;
}
return switchInline(stream, state, state.inline);
}
function htmlBlock(stream, state) {
var style = htmlMode.token(stream, state.htmlState);
if (style === 'tag' && state.htmlState.type !== 'openTag' && !state.htmlState.context) {
state.f = inlineNormal;
state.block = blockNormal;
}
return style;
}
// Inline
function getType(state) {
// Set defaults
returnValue = '';
// Strong / Emphasis
if(state.strong){
if(state.em){
returnValue += (returnValue ? ' ' : '') + emstrong;
} else {
returnValue += (returnValue ? ' ' : '') + strong;
}
} else {
if(state.em){
returnValue += (returnValue ? ' ' : '') + em;
}
}
// Header
if(state.header){
returnValue += (returnValue ? ' ' : '') + header;
}
// Quotes
if(state.quote){
returnValue += (returnValue ? ' ' : '') + quote;
}
// Check valud and return
if(!returnValue){
returnValue = null;
}
return returnValue;
}
function handleText(stream, state) {
if (stream.match(textRE, true)) {
return getType(state);
}
return undefined;
}
function inlineNormal(stream, state) {
var style = state.text(stream, state)
if (typeof style !== 'undefined')
return style;
var ch = stream.next();
if (ch === '\\') {
stream.next();
return getType(state);
}
if (ch === '`') {
return switchInline(stream, state, inlineElement(code, '`'));
}
if (ch === '[') {
return switchInline(stream, state, linkText);
}
if (ch === '<' && stream.match(/^\w/, false)) {
stream.backUp(1);
return switchBlock(stream, state, htmlBlock);
}
var t = getType(state);
if (ch === '*' || ch === '_') {
if (stream.eat(ch)) {
return (state.strong = !state.strong) ? getType(state) : t;
}
return (state.em = !state.em) ? getType(state) : t;
}
return getType(state);
}
function linkText(stream, state) {
while (!stream.eol()) {
var ch = stream.next();
if (ch === '\\') stream.next();
if (ch === ']') {
state.inline = state.f = linkHref;
return linktext;
}
}
return linktext;
}
function linkHref(stream, state) {
stream.eatSpace();
var ch = stream.next();
if (ch === '(' || ch === '[') {
return switchInline(stream, state, inlineElement(linkhref, ch === '(' ? ')' : ']'));
}
return 'error';
}
function footnoteLink(stream, state) {
if (stream.match(/^[^\]]*\]:/, true)) {
state.f = footnoteUrl;
return linktext;
}
return switchInline(stream, state, inlineNormal);
}
function footnoteUrl(stream, state) {
stream.eatSpace();
stream.match(/^[^\s]+/, true);
state.f = state.inline = inlineNormal;
return linkhref;
}
function inlineRE(endChar) {
if (!inlineRE[endChar]) {
// match any not-escaped-non-endChar and any escaped char
// then match endChar or eol
inlineRE[endChar] = new RegExp('^(?:[^\\\\\\' + endChar + ']|\\\\.)*(?:\\' + endChar + '|$)');
}
return inlineRE[endChar];
}
function inlineElement(type, endChar, next) {
next = next || inlineNormal;
return function(stream, state) {
stream.match(inlineRE(endChar));
state.inline = state.f = next;
return type;
};
}
return {
startState: function() {
return {
f: blockNormal,
block: blockNormal,
htmlState: htmlMode.startState(),
indentation: 0,
inline: inlineNormal,
text: handleText,
em: false,
strong: false,
header: false,
quote: false
};
},
copyState: function(s) {
return {
f: s.f,
block: s.block,
htmlState: CodeMirror.copyState(htmlMode, s.htmlState),
indentation: s.indentation,
inline: s.inline,
text: s.text,
em: s.em,
strong: s.strong,
header: s.header,
quote: s.quote
};
},
token: function(stream, state) {
if (stream.sol()) {
// Reset EM state
state.em = false;
// Reset STRONG state
state.strong = false;
// Reset state.header
state.header = false;
// Reset state.quote
state.quote = false;
state.f = state.block;
var previousIndentation = state.indentation
, currentIndentation = 0;
while (previousIndentation > 0) {
if (stream.eat(' ')) {
previousIndentation--;
currentIndentation++;
} else if (previousIndentation >= 4 && stream.eat('\t')) {
previousIndentation -= 4;
currentIndentation += 4;
} else {
break;
}
}
state.indentation = currentIndentation;
if (currentIndentation > 0) return null;
}
return state.f(stream, state);
},
getType: getType
};
});
CodeMirror.defineMIME("text/x-markdown", "markdown");
CodeMirror.defineMode("python", function(conf, parserConf) {
var ERRORCLASS = 'error';
function wordRegexp(words) {
return new RegExp("^((" + words.join(")|(") + "))\\b");
}
var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!]");
var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]');
var doubleOperators = new RegExp("^((==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))");
var doubleDelimiters = new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
var tripleDelimiters = new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))");
var identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*");
var wordOperators = wordRegexp(['and', 'or', 'not', 'is', 'in']);
var commonkeywords = ['as', 'assert', 'break', 'class', 'continue',
'def', 'del', 'elif', 'else', 'except', 'finally',
'for', 'from', 'global', 'if', 'import',
'lambda', 'pass', 'raise', 'return',
'try', 'while', 'with', 'yield'];
var commonBuiltins = ['abs', 'all', 'any', 'bin', 'bool', 'bytearray', 'callable', 'chr',
'classmethod', 'compile', 'complex', 'delattr', 'dict', 'dir', 'divmod',
'enumerate', 'eval', 'filter', 'float', 'format', 'frozenset',
'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id',
'input', 'int', 'isinstance', 'issubclass', 'iter', 'len',
'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next',
'object', 'oct', 'open', 'ord', 'pow', 'property', 'range',
'repr', 'reversed', 'round', 'set', 'setattr', 'slice',
'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple',
'type', 'vars', 'zip', '__import__', 'NotImplemented',
'Ellipsis', '__debug__'];
var py2 = {'builtins': ['apply', 'basestring', 'buffer', 'cmp', 'coerce', 'execfile',
'file', 'intern', 'long', 'raw_input', 'reduce', 'reload',
'unichr', 'unicode', 'xrange', 'False', 'True', 'None'],
'keywords': ['exec', 'print']};
var py3 = {'builtins': ['ascii', 'bytes', 'exec', 'print'],
'keywords': ['nonlocal', 'False', 'True', 'None']};
if (!!parserConf.version && parseInt(parserConf.version, 10) === 3) {
commonkeywords = commonkeywords.concat(py3.keywords);
commonBuiltins = commonBuiltins.concat(py3.builtins);
var stringPrefixes = new RegExp("^(([rb]|(br))?('{3}|\"{3}|['\"]))", "i");
} else {
commonkeywords = commonkeywords.concat(py2.keywords);
commonBuiltins = commonBuiltins.concat(py2.builtins);
var stringPrefixes = new RegExp("^(([rub]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i");
}
var keywords = wordRegexp(commonkeywords);
var builtins = wordRegexp(commonBuiltins);
var indentInfo = null;
// tokenizers
function tokenBase(stream, state) {
// Handle scope changes
if (stream.sol()) {
var scopeOffset = state.scopes[0].offset;
if (stream.eatSpace()) {
var lineOffset = stream.indentation();
if (lineOffset > scopeOffset) {
indentInfo = 'indent';
} else if (lineOffset < scopeOffset) {
indentInfo = 'dedent';
}
return null;
} else {
if (scopeOffset > 0) {
dedent(stream, state);
}
}
}
if (stream.eatSpace()) {
return null;
}
var ch = stream.peek();
// Handle Comments
if (ch === '#') {
stream.skipToEnd();
return 'comment';
}
// Handle Number Literals
if (stream.match(/^[0-9\.]/, false)) {
var floatLiteral = false;
// Floats
if (stream.match(/^\d*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true; }
if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; }
if (stream.match(/^\.\d+/)) { floatLiteral = true; }
if (floatLiteral) {
// Float literals may be "imaginary"
stream.eat(/J/i);
return 'number';
}
// Integers
var intLiteral = false;
// Hex
if (stream.match(/^0x[0-9a-f]+/i)) { intLiteral = true; }
// Binary
if (stream.match(/^0b[01]+/i)) { intLiteral = true; }
// Octal
if (stream.match(/^0o[0-7]+/i)) { intLiteral = true; }
// Decimal
if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) {
// Decimal literals may be "imaginary"
stream.eat(/J/i);
// TODO - Can you have imaginary longs?
intLiteral = true;
}
// Zero by itself with no other piece of number.
if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; }
if (intLiteral) {
// Integer literals may be "long"
stream.eat(/L/i);
return 'number';
}
}
// Handle Strings
if (stream.match(stringPrefixes)) {
state.tokenize = tokenStringFactory(stream.current());
return state.tokenize(stream, state);
}
// Handle operators and Delimiters
if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) {
return null;
}
if (stream.match(doubleOperators)
|| stream.match(singleOperators)
|| stream.match(wordOperators)) {
return 'operator';
}
if (stream.match(singleDelimiters)) {
return null;
}
if (stream.match(keywords)) {
return 'keyword';
}
if (stream.match(builtins)) {
return 'builtin';
}
if (stream.match(identifiers)) {
return 'variable';
}
// Handle non-detected items
stream.next();
return ERRORCLASS;
}
function tokenStringFactory(delimiter) {
while ('rub'.indexOf(delimiter.charAt(0).toLowerCase()) >= 0) {
delimiter = delimiter.substr(1);
}
var singleline = delimiter.length == 1;
var OUTCLASS = 'string';
return function tokenString(stream, state) {
while (!stream.eol()) {
stream.eatWhile(/[^'"\\]/);
if (stream.eat('\\')) {
stream.next();
if (singleline && stream.eol()) {
return OUTCLASS;
}
} else if (stream.match(delimiter)) {
state.tokenize = tokenBase;
return OUTCLASS;
} else {
stream.eat(/['"]/);
}
}
if (singleline) {
if (parserConf.singleLineStringErrors) {
return ERRORCLASS;
} else {
state.tokenize = tokenBase;
}
}
return OUTCLASS;
};
}
function indent(stream, state, type) {
type = type || 'py';
var indentUnit = 0;
if (type === 'py') {
if (state.scopes[0].type !== 'py') {
state.scopes[0].offset = stream.indentation();
return;
}
for (var i = 0; i < state.scopes.length; ++i) {
if (state.scopes[i].type === 'py') {
indentUnit = state.scopes[i].offset + conf.indentUnit;
break;
}
}
} else {
indentUnit = stream.column() + stream.current().length;
}
state.scopes.unshift({
offset: indentUnit,
type: type
});
}
function dedent(stream, state, type) {
type = type || 'py';
if (state.scopes.length == 1) return;
if (state.scopes[0].type === 'py') {
var _indent = stream.indentation();
var _indent_index = -1;
for (var i = 0; i < state.scopes.length; ++i) {
if (_indent === state.scopes[i].offset) {
_indent_index = i;
break;
}
}
if (_indent_index === -1) {
return true;
}
while (state.scopes[0].offset !== _indent) {
state.scopes.shift();
}
return false
} else {
if (type === 'py') {
state.scopes[0].offset = stream.indentation();
return false;
} else {
if (state.scopes[0].type != type) {
return true;
}
state.scopes.shift();
return false;
}
}
}
function tokenLexer(stream, state) {
indentInfo = null;
var style = state.tokenize(stream, state);
var current = stream.current();
// Handle '.' connected identifiers
if (current === '.') {
style = state.tokenize(stream, state);
current = stream.current();
if (style === 'variable' || style === 'builtin') {
return 'variable';
} else {
return ERRORCLASS;
}
}
// Handle decorators
if (current === '@') {
style = state.tokenize(stream, state);
current = stream.current();
if (style === 'variable'
|| current === '@staticmethod'
|| current === '@classmethod') {
return 'meta';
} else {
return ERRORCLASS;
}
}
// Handle scope changes.
if (current === 'pass' || current === 'return') {
state.dedent += 1;
}
if (current === 'lambda') state.lambda = true;
if ((current === ':' && !state.lambda && state.scopes[0].type == 'py')
|| indentInfo === 'indent') {
indent(stream, state);
}
var delimiter_index = '[({'.indexOf(current);
if (delimiter_index !== -1) {
indent(stream, state, '])}'.slice(delimiter_index, delimiter_index+1));
}
if (indentInfo === 'dedent') {
if (dedent(stream, state)) {
return ERRORCLASS;
}
}
delimiter_index = '])}'.indexOf(current);
if (delimiter_index !== -1) {
if (dedent(stream, state, current)) {
return ERRORCLASS;
}
}
if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'py') {
if (state.scopes.length > 1) state.scopes.shift();
state.dedent -= 1;
}
return style;
}
var external = {
startState: function(basecolumn) {
return {
tokenize: tokenBase,
scopes: [{offset:basecolumn || 0, type:'py'}],
lastToken: null,
lambda: false,
dedent: 0
};
},
token: function(stream, state) {
var style = tokenLexer(stream, state);
state.lastToken = {style:style, content: stream.current()};
if (stream.eol() && stream.lambda) {
state.lambda = false;
}
return style;
},
indent: function(state, textAfter) {
if (state.tokenize != tokenBase) {
return 0;
}
return state.scopes[0].offset;
}
};
return external;
});
CodeMirror.defineMIME("text/x-python", "python");
CodeMirror.defineMode("xml", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var Kludges = parserConfig.htmlMode ? {
autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true,
"meta": true, "col": true, "frame": true, "base": true, "area": true},
doNotIndent: {"pre": true},
allowUnquoted: true,
allowMissing: false
} : {autoSelfClosers: {}, doNotIndent: {}, allowUnquoted: false, allowMissing: false};
var alignCDATA = parserConfig.alignCDATA;
// Return variables for tokenizers
var tagName, type;
function inText(stream, state) {
function chain(parser) {
state.tokenize = parser;
return parser(stream, state);
}
var ch = stream.next();
if (ch == "<") {
if (stream.eat("!")) {
if (stream.eat("[")) {
if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
else return null;
}
else if (stream.match("--")) return chain(inBlock("comment", "-->"));
else if (stream.match("DOCTYPE", true, true)) {
stream.eatWhile(/[\w\._\-]/);
return chain(doctype(1));
}
else return null;
}
else if (stream.eat("?")) {
stream.eatWhile(/[\w\._\-]/);
state.tokenize = inBlock("meta", "?>");
return "meta";
}
else {
type = stream.eat("/") ? "closeTag" : "openTag";
stream.eatSpace();
tagName = "";
var c;
while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c;
state.tokenize = inTag;
return "tag";
}
}
else if (ch == "&") {
var ok;
if (stream.eat("#")) {
if (stream.eat("x")) {
ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
} else {
ok = stream.eatWhile(/[\d]/) && stream.eat(";");
}
} else {
ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
}
return ok ? "atom" : "error";
}
else {
stream.eatWhile(/[^&<]/);
return null;
}
}
function inTag(stream, state) {
var ch = stream.next();
if (ch == ">" || (ch == "/" && stream.eat(">"))) {
state.tokenize = inText;
type = ch == ">" ? "endTag" : "selfcloseTag";
return "tag";
}
else if (ch == "=") {
type = "equals";
return null;
}
else if (/[\'\"]/.test(ch)) {
state.tokenize = inAttribute(ch);
return state.tokenize(stream, state);
}
else {
stream.eatWhile(/[^\s\u00a0=<>\"\'\/?]/);
return "word";
}
}
function inAttribute(quote) {
return function(stream, state) {
while (!stream.eol()) {
if (stream.next() == quote) {
state.tokenize = inTag;
break;
}
}
return "string";
};
}
function inBlock(style, terminator) {
return function(stream, state) {
while (!stream.eol()) {
if (stream.match(terminator)) {
state.tokenize = inText;
break;
}
stream.next();
}
return style;
};
}
function doctype(depth) {
return function(stream, state) {
var ch;
while ((ch = stream.next()) != null) {
if (ch == "<") {
state.tokenize = doctype(depth + 1);
return state.tokenize(stream, state);
} else if (ch == ">") {
if (depth == 1) {
state.tokenize = inText;
break;
} else {
state.tokenize = doctype(depth - 1);
return state.tokenize(stream, state);
}
}
}
return "meta";
};
}
var curState, setStyle;
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function pushContext(tagName, startOfLine) {
var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent);
curState.context = {
prev: curState.context,
tagName: tagName,
indent: curState.indented,
startOfLine: startOfLine,
noIndent: noIndent
};
}
function popContext() {
if (curState.context) curState.context = curState.context.prev;
}
function element(type) {
if (type == "openTag") {
curState.tagName = tagName;
return cont(attributes, endtag(curState.startOfLine));
} else if (type == "closeTag") {
var err = false;
if (curState.context) {
err = curState.context.tagName != tagName;
} else {
err = true;
}
if (err) setStyle = "error";
return cont(endclosetag(err));
}
return cont();
}
function endtag(startOfLine) {
return function(type) {
if (type == "selfcloseTag" ||
(type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase())))
return cont();
if (type == "endTag") {pushContext(curState.tagName, startOfLine); return cont();}
return cont();
};
}
function endclosetag(err) {
return function(type) {
if (err) setStyle = "error";
if (type == "endTag") { popContext(); return cont(); }
setStyle = "error";
return cont(arguments.callee);
}
}
function attributes(type) {
if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);}
if (type == "endTag" || type == "selfcloseTag") return pass();
setStyle = "error";
return cont(attributes);
}
function attribute(type) {
if (type == "equals") return cont(attvalue, attributes);
if (!Kludges.allowMissing) setStyle = "error";
return (type == "endTag" || type == "selfcloseTag") ? pass() : cont();
}
function attvalue(type) {
if (type == "string") return cont(attvaluemaybe);
if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();}
setStyle = "error";
return (type == "endTag" || type == "selfCloseTag") ? pass() : cont();
}
function attvaluemaybe(type) {
if (type == "string") return cont(attvaluemaybe);
else return pass();
}
return {
startState: function() {
return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, context: null};
},
token: function(stream, state) {
if (stream.sol()) {
state.startOfLine = true;
state.indented = stream.indentation();
}
if (stream.eatSpace()) return null;
setStyle = type = tagName = null;
var style = state.tokenize(stream, state);
state.type = type;
if ((style || type) && style != "comment") {
curState = state;
while (true) {
var comb = state.cc.pop() || element;
if (comb(type || style)) break;
}
}
state.startOfLine = false;
return setStyle || style;
},
indent: function(state, textAfter, fullLine) {
var context = state.context;
if ((state.tokenize != inTag && state.tokenize != inText) ||
context && context.noIndent)
return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
if (alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
if (context && /^<\//.test(textAfter))
context = context.prev;
while (context && !context.startOfLine)
context = context.prev;
if (context) return context.indent + indentUnit;
else return 0;
},
compareStates: function(a, b) {
if (a.indented != b.indented || a.tokenize != b.tokenize) return false;
for (var ca = a.context, cb = b.context; ; ca = ca.prev, cb = cb.prev) {
if (!ca || !cb) return ca == cb;
if (ca.tagName != cb.tagName) return false;
}
},
electricChars: "/"
};
});
CodeMirror.defineMIME("application/xml", "xml");
CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
form {
font-size: 0em;
font-size: 1em;
label {
color: $base-font-color;
......
......@@ -17,7 +17,7 @@ def url_class(url):
<li class="book"><a href="${reverse('book', args=[course.id])}" class="${url_class('book')}">Textbook</a></li>
<li class="discussion"><a href="${reverse('questions')}">Discussion</a></li>
% endif
<li class="wiki"><a href="${reverse('wiki_root')}" class="${url_class('wiki')}">Wiki</a></li>
<li class="wiki"><a href="${reverse('wiki_root', args=[course.id])}" class="${url_class('wiki')}">Wiki</a></li>
% if user.is_authenticated():
<li class="profile"><a href="${reverse('profile', args=[course.id])}" class="${url_class('profile')}">Profile</a></li>
% endif
......
##This file is based on the template from the SimpleWiki source which carries the GPL license
<%inherit file="main.html"/>
<%namespace name='static' file='static_content.html'/>
<%inherit file="../main.html"/>
<%namespace name='static' file='../static_content.html'/>
<%block name="headextra">
<script type="text/javascript" src="${static.url('js/simplewiki/bsn.AutoSuggest_c_2.0.js')}"></script>
<%!
<%!
from django.core.urlresolvers import reverse
%>
from simplewiki.views import wiki_reverse
%>
<%block name="headextra">
<script type="text/javascript" src="${static.url('js/simplewiki-AutoSuggest_c_2.0.js')}"></script>
<script type="text/javascript">
function set_related_article_id(s) {
......@@ -19,7 +20,7 @@
var x = window.onload;
window.onload = function(){
var options = {
script: "${reverse("search_related", args=[wiki_article.get_url()] )}/?self=${wiki_article.pk}&",
script: "${ wiki_reverse('search_related', wiki_article, course)}/?self=${wiki_article.pk}&",
json: true,
varname: "query",
maxresults: 35,
......@@ -69,23 +70,19 @@
<%block name="bodyextra">
<%include file="course_navigation.html" args="active_page='wiki'" />
%if course:
<%include file="../course_navigation.html" args="active_page='wiki'" />
%endif
<section class="main-content">
<div class="wiki-wrapper">
<%block name="wiki_panel">
<div aria-label="Wiki Navigation" id="wiki_panel">
<h2>Course Wiki</h2>
<%
if (wiki_article is not UNDEFINED):
baseURL = reverse("wiki_view", args=[wiki_article.get_url()])
else:
baseURL = reverse("wiki_view", args=["/"])
%>
<ul class="action">
<li>
<h3>
<a href="${reverse("wiki_list_articles", args=[])}">All Articles</a>
<a href="${wiki_reverse("wiki_list_articles", course=course, namespace=namespace)}">All Articles</a>
</h3>
</li>
......@@ -96,36 +93,25 @@
<div id="wiki_create_form">
<%
theaction = "this.wiki_article_name.value.replace(/([^a-zA-Z0-9\-])/g, '')"
baseURL = reverse("wiki_create", args=["/"])
baseURL = wiki_reverse("wiki_create", course=course, kwargs={"article_path" : namespace + "/" })
%>
<form method="GET" onsubmit="this.action='${baseURL + "' + " + theaction};">
<form method="GET" onsubmit="this.action='${baseURL}' + this.wiki_article_name.value.replace(/([^a-zA-Z0-9\-])/g, '');">
<div>
<label for="id_wiki_article_name">Title of article</label>
<input type="text" name="wiki_article_name" id="id_wiki_article_name" /><br/>
<!-- <label for="id_wiki_article_is_child">Create as a child of current article</label> -->
<!-- <input type="checkbox" name="wiki_article_is_child" id="id_wiki_artcile_is_child" disabled="true" ${ 'checked="checked"' if wiki_article is not UNDEFINED else ""}> -->
</div>
<ul>
<li>
<input type="submit" class="button" value="Create" style="display: inline-block; margin-right: 2px; font-weight: bold;" />
</li>
</ul>
</form>
</div>
</li>
<li class="search">
<form method="POST" action='${reverse("wiki_search_articles", args=[])}'>
<form method="GET" action='${wiki_reverse("wiki_search_articles", course=course, namespace=namespace)}'>
<label class="wiki_box_title">Search</label>
<div style="display:none">
<input type="hidden" name="csrfmiddlewaretoken" value="${csrf_token}"/>
</div>
<input type="text" placeholder="Search" name="value" id="wiki_search_input" style="width: 71%" value="${wiki_search_query if wiki_search_query is not UNDEFINED else '' |h}"/>
<input type="submit" id="wiki_search_input_submit" value="Go!" style="width: 20%" />
</form>
......@@ -148,15 +134,15 @@
<ul>
<li>
<input type="button" onclick="javascript:location.href='${reverse("wiki_view", args=[wiki_article.get_url()])}'" value="View" class="view" />
<a href="${ wiki_reverse('wiki_view', wiki_article, course)}" class="view">View</a>
</li>
<li>
<input type="button" onclick="javascript:location.href='${reverse("wiki_edit", args=[wiki_article.get_url()])}'" value="Edit" ${'disabled="true"' if not wiki_write else ""} class="edit"/>
<a href="${ wiki_reverse('wiki_edit', wiki_article, course)}" class="edit">Edit</a>
</li>
<li>
<input type="button" onclick="javascript:location.href='${reverse("wiki_history", args=[wiki_article.get_url(),1])}'" value="History" class="button history" />
<a href="${ wiki_reverse('wiki_history', wiki_article, course)}" class="history">History</a>
</li>
</ul>
</header>
......
......@@ -20,35 +20,35 @@ ${"Edit " + wiki_title + " - " if wiki_title is not UNDEFINED else ""}MITx 6.002
</%block>
<%block name="wiki_head">
<script type="text/javascript" src="${ settings.LIB_URL }CodeMirror/codemirror.js"></script>
<link rel="stylesheet" href="${ settings.LIB_URL }CodeMirror/codemirror.css" />
<script type="text/javascript" src="${ settings.LIB_URL }vendor/CodeMirror/codemirror.js"></script>
<link rel="stylesheet" href="${ settings.LIB_URL }vendor/CodeMirror/codemirror.css" />
<script type="text/javascript" src="${ settings.LIB_URL }CodeMirror/xml.js"></script>
<script type="text/javascript" src="${ settings.LIB_URL }CodeMirror/mitx_markdown.js"></script>
<script type="text/javascript" src="${ settings.LIB_URL }vendor/CodeMirror/xml.js"></script>
<script type="text/javascript" src="${ settings.LIB_URL }vendor/CodeMirror/mitx_markdown.js"></script>
<script>
$(function(){
$(document).ready(function() {
var editor = CodeMirror.fromTextArea(document.getElementById("id_contents"), {
mode: 'mitx_markdown',
matchBrackets: true,
theme: "default",
lineWrapping: true,
});
//Store the inital contents so we can compare for unsaved changes
var initial_contents = editor.getValue();
window.onbeforeunload = function askConfirm() { //Warn the user before they navigate away
if ( editor.getValue() != initial_contents ) {
return "You have made changes to the article that have not been saved yet.";
}
};
$("#submit_edit").click(function() {
initial_contents = editor.getValue();
});
//TODO: Re-enable this once the styling supports it
// var editor = CodeMirror.fromTextArea(document.getElementById("id_contents"), {
// mode: 'mitx_markdown',
// matchBrackets: true,
// theme: "default",
// lineWrapping: true,
// });
//
// //Store the inital contents so we can compare for unsaved changes
// var initial_contents = editor.getValue();
//
// window.onbeforeunload = function askConfirm() { //Warn the user before they navigate away
// if ( editor.getValue() != initial_contents ) {
// return "You have made changes to the article that have not been saved yet.";
// }
// };
//
// $("#submit_edit").click(function() {
// initial_contents = editor.getValue();
// });
});
});
......
......@@ -3,7 +3,7 @@
<%inherit file="simplewiki_base.html"/>
<%!
from django.core.urlresolvers import reverse
from simplewiki.views import wiki_reverse
%>
<%block name="title"><title>Wiki Error – MITx 6.002x</title></%block>
......@@ -21,30 +21,17 @@ ${wiki_error}
%endif
%if wiki_err_notfound is not UNDEFINED:
%if wiki_url is not UNDEFINED:
<p>
The page you requested could not be found.
Click <a href="${reverse("wiki_create", args=[wiki_url])}">here</a> to create it.
Click <a href="${wiki_reverse("wiki_create", course=course, kwargs={'article_path' : article_path})}">here</a> to create it.
</p>
%else:
%elif wiki_err_no_namespace is not UNDEFINED and wiki_err_no_namespace:
<p>
Or maybe rather: Congratulations! It seems that there's no root
article, which is probably because you just installed simple-wiki
and your installation is working. Now you can create the root article.
Click <a href="{% url wiki_create "" %}">here</a> to create it.
You must specify a namespace to create an article in.
</p>
%endif
%else:
%if wiki_err_noparent is not UNDEFINED:
<p>
You cannot create this page, because its parent
does not exist. Click <a href="${reverse("wiki_create", args=[wiki_url_parent])}">here</a>
to create it.
</p>
%else:
%if wiki_err_keyword is not UNDEFINED and wiki_err_keyword:
%elif wiki_err_bad_namespace is not UNDEFINED and wiki_err_bad_namespace:
<p>
The page you're trying to create <b>${wiki_url}</b> starts with <b>_</b>, which is reserved for internal use.
The namespace for this article does not exist. This article cannot be created.
</p>
%elif wiki_err_locked is not UNDEFINED and wiki_err_locked:
<p>
......@@ -75,7 +62,7 @@ ${wiki_error}
</p>
%elif wiki_err_deleted is not UNDEFINED and wiki_err_deleted:
<p>
The article you tried to access has been deleted. You may be able to restore it to an earlier version in its <a href="${reverse("wiki_history", args=[wiki_article.get_url(),1])}">history</a>, or <a href="${reverse("wiki_edit", args=[wiki_article.get_url()])}">create a new version</a>.
The article you tried to access has been deleted. You may be able to restore it to an earlier version in its <a href="${wiki_reverse("wiki_history", wiki_article, course)}">history</a>, or <a href="${wiki_reverse("wiki_edit", wiki_article, course)}">create a new version</a>.
</p>
%elif wiki_err_norevision is not UNDEFINED:
<p>
......@@ -86,8 +73,6 @@ ${wiki_error}
An error has occured.
</p>
%endif
%endif
%endif
</div>
</%block>
......
......@@ -6,6 +6,7 @@
<%!
from django.core.urlresolvers import reverse
from simplewiki.views import wiki_reverse
%>
<%block name="wiki_page_title">
......@@ -64,10 +65,10 @@ ${ wiki_article.title }
<tr>
<td colspan="4">
%if wiki_prev_page:
<a href="${reverse("wiki_history", args=[wiki_article.get_url(), wiki_prev_page])}">Previous page</a>
<a href="${wiki_reverse('wiki_history', wiki_article, course, kwargs={'page' : wiki_prev_page})}">Previous page</a>
%endif
%if wiki_next_page:
<a href="${reverse("wiki_history", args=[wiki_article.get_url(), wiki_next_page])}">Next page</a>
<a href="${wiki_reverse('wiki_history', wiki_article, course, kwargs={'page' : wiki_next_page})}">Next page</a>
%endif
</td>
</tr>
......
......@@ -5,7 +5,7 @@
<%block name="title"><title>Wiki - Revision feed - MITx 6.002x</title></%block>
<%!
from django.core.urlresolvers import reverse
from simplewiki.views import wiki_reverse
%>
<%block name="wiki_page_title">
......@@ -29,7 +29,7 @@
<% loopCount += 1 %>
<tr style="border-top: 1px" class="${'dark ' if (loopCount % 2) == 0 else ''}${'deleted ' if (revision.deleted==2) else ''}" >
<td width="15px">
<a href="${reverse('wiki_view_revision',args=[revision.counter, revision.article.get_url()])}"> ${revision.article.title} - ${revision}</a>
<a href="${wiki_reverse('wiki_view_revision', revision.article, course, kwargs={'revision_number' : revision.counter})}"> ${revision.article.title} - ${revision}</a>
</td>
<td>
${ revision.revision_text if revision.revision_text else "<i>None</i>" }</td>
......@@ -50,10 +50,10 @@
<tr>
<td colspan="4">
%if wiki_prev_page:
<a href="${reverse("wiki_revision_feed", args=[wiki_prev_page])}">Previous page</a>
<a href="${wiki_reverse("wiki_revision_feed", course=course, namespace=namespace, kwargs={'page': wiki_prev_page})}">Previous page</a>
%endif
%if wiki_next_page:
<a href="${reverse("wiki_revision_feed", args=[wiki_next_page])}">Next page</a>
<a href="${wiki_reverse("wiki_revision_feed", course=course, namespace=namespace, kwargs={'page': wiki_next_page})}">Next page</a>
%endif
</td>
</tr>
......
......@@ -5,7 +5,7 @@
<%block name="title"><title>Wiki - Search Results - MITx 6.002x</title></%block>
<%!
from django.core.urlresolvers import reverse
from simplewiki.views import wiki_reverse
%>
<%block name="wiki_page_title">
......@@ -23,7 +23,7 @@ Displaying all articles
<ul class="article-list">
%for article in wiki_search_results:
<% article_deleted = not article.current_revision.deleted == 0 %>
<li><h3><a href="${reverse("wiki_view", args=[article.get_url()])}">${article.title} ${'(Deleted)' if article_deleted else ''}</a></h3></li>
<li><h3><a href="${wiki_reverse("wiki_view", article, course)}">${article.title} ${'(Deleted)' if article_deleted else ''}</a></h3></li>
%endfor
%if not wiki_search_results:
......
......@@ -52,7 +52,6 @@ if settings.PERFSTATS:
if settings.COURSEWARE_ENABLED:
urlpatterns += (
url(r'^wiki/', include('simplewiki.urls')),
url(r'^masquerade/', include('masquerade.urls')),
url(r'^jumpto/(?P<probname>[^/]+)/$', 'courseware.views.jump_to'),
url(r'^modx/(?P<id>.*?)/(?P<dispatch>[^/]*)$', 'courseware.module_render.modx_dispatch'), #reset_problem'),
......@@ -81,6 +80,12 @@ if settings.COURSEWARE_ENABLED:
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/about$', 'student.views.course_info', name="about_course"),
)
# Multicourse wiki
urlpatterns += (
url(r'^wiki/', include('simplewiki.urls')),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/wiki/', include('simplewiki.urls')),
)
if settings.ENABLE_MULTICOURSE:
urlpatterns += (url(r'^mitxhome$', 'multicourse.views.mitxhome'),)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment