assigning tickets, new ui
authorRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Wed, 15 Jun 2011 14:12:13 +0000 (16:12 +0200)
committerRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Wed, 15 Jun 2011 14:12:13 +0000 (16:12 +0200)
17 files changed:
apps/dvcs/migrations/0002_auto__add_field_document_user__add_field_document_stage.py [new file with mode: 0644]
apps/dvcs/models.py
apps/wiki/forms.py
apps/wiki/helpers.py
apps/wiki/models.py
apps/wiki/templates/wiki/base.html
apps/wiki/templates/wiki/book_detail.html
apps/wiki/templates/wiki/chunk_list_item.html [new file with mode: 0755]
apps/wiki/templates/wiki/document_list.html
apps/wiki/templates/wiki/main_tabs.html [new file with mode: 0755]
apps/wiki/templatetags/wiki.py
apps/wiki/urls.py
apps/wiki/views.py
redakcja/settings/common.py
redakcja/static/css/filelist.css
redakcja/static/img/wl-orange.png [new file with mode: 0644]
requirements.txt

diff --git a/apps/dvcs/migrations/0002_auto__add_field_document_user__add_field_document_stage.py b/apps/dvcs/migrations/0002_auto__add_field_document_user__add_field_document_stage.py
new file mode 100644 (file)
index 0000000..41ab9b2
--- /dev/null
@@ -0,0 +1,96 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Adding field 'Document.user'
+        db.add_column('dvcs_document', 'user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True), keep_default=False)
+
+        # Adding field 'Document.stage'
+        db.add_column('dvcs_document', 'stage', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['dvcs.Tag'], null=True), keep_default=False)
+
+
+    def backwards(self, orm):
+        
+        # Deleting field 'Document.user'
+        db.delete_column('dvcs_document', 'user_id')
+
+        # Deleting field 'Document.stage'
+        db.delete_column('dvcs_document', 'stage_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', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'dvcs.change': {
+            'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'Change'},
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+            'author_desc': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['dvcs.Change']"}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['dvcs.Change']"}),
+            'patch': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+            'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['dvcs.Tag']", 'symmetrical': 'False'}),
+            'tree': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dvcs.Document']"})
+        },
+        'dvcs.document': {
+            'Meta': {'object_name': 'Document'},
+            'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_documents'", 'null': 'True', 'to': "orm['auth.User']"}),
+            'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['dvcs.Change']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['dvcs.Tag']", 'null': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
+        },
+        'dvcs.tag': {
+            'Meta': {'ordering': "['ordering']", 'object_name': 'Tag'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'ordering': ('django.db.models.fields.IntegerField', [], {}),
+            'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'})
+        }
+    }
+
+    complete_apps = ['dvcs']
index 262472a..a72556f 100644 (file)
@@ -38,6 +38,9 @@ class Tag(models.Model):
     def listener_changed(sender, instance, **kwargs):
         sender._object_cache = {}
 
+    def next(self):
+        Tag.objects.filter(ordering__gt=self.ordering)
+
 models.signals.pre_save.connect(Tag.listener_changed, sender=Tag)
 
 
@@ -168,12 +171,16 @@ class Document(models.Model):
     """
         File in repository.        
     """
-    creator = models.ForeignKey(User, null=True, blank=True, editable=False)
+    creator = models.ForeignKey(User, null=True, blank=True, editable=False,
+                related_name="created_documents")
     head = models.ForeignKey(Change,
                     null=True, blank=True, default=None,
                     help_text=_("This document's current head."),
                     editable=False)
 
+    user = models.ForeignKey(User, null=True, blank=True)
+    stage = models.ForeignKey(Tag, null=True, blank=True)
+
     def __unicode__(self):
         return u"{0}, HEAD: {1}".format(self.id, self.head_id)
 
index 55b1e54..3626b6d 100644 (file)
@@ -40,7 +40,8 @@ class DocumentCreateForm(forms.ModelForm):
 
     class Meta:
         model = Book
-        exclude = ['gallery']
+        exclude = ['gallery', 'parent', 'parent_number']
+        prepopulated_fields = {'slug': ['title']}
 
     def clean(self):
         super(DocumentCreateForm, self).clean()
@@ -164,7 +165,7 @@ class ChunkForm(forms.ModelForm):
             chunk = Chunk.objects.get(book=self.instance.book, slug=slug)
         except Chunk.DoesNotExist:
             return slug
-        if chunk == self:
+        if chunk == self.instance:
             return slug
         raise forms.ValidationError(_('Chunk with this slug already exists'))
 
index f072ef9..ee26c5a 100644 (file)
@@ -129,3 +129,17 @@ def recursive_groupby(iterable):
             grouper = None
 
     return list(_generator(iterable))
+
+
+def active_tab(tab):
+    """
+        View decorator, which puts tab info on a request.
+    """
+    def wrapper(f):
+        @wraps(f)
+        def wrapped(request, *args, **kwargs):
+            request.wiki_active_tab = tab
+            return f(request, *args, **kwargs)
+        return wrapped
+    return wrapper
+
index 7887e5d..9eb77a5 100644 (file)
@@ -188,6 +188,11 @@ class Chunk(dvcs_models.Document):
                 creator=creator, slug=slug, comment=comment)
         return new_chunk
 
+    def list_html(self):
+        _list_html = render_to_string('wiki/chunk_list_item.html',
+                {'chunk': self})
+        return mark_safe(_list_html)
+
     @staticmethod
     def listener_saved(sender, instance, created, **kwargs):
         if instance.book:
index f88fac3..85abaaf 100644 (file)
@@ -1,18 +1,36 @@
-{% extends "base.html" %}
 {% load compressed i18n %}
+{% load wiki %}
+<!DOCTYPE html>
+<html>
+<head>
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+    {% compressed_css 'listing' %}
+    <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}style.css" />
+    <title>{% block title %}{% trans "Platforma Redakcyjna" %}{% endblock title %}</title>
+</head>
+<body>
 
-{% block title %}{{ document_name }} - {{ block.super }}{% endblock %}
+<div id="tabs-nav">
 
-{% block extrahead %}
-{% compressed_css 'listing' %}
-{% endblock %}
+    <img id="logo" src="{{ STATIC_URL }}img/wl-orange.png" />
 
-{% block extrabody %}
-{% compressed_js 'listing' %}
-{% endblock %}
+    <div id="tabs-nav-left">
+        {% main_tabs %}
+        <!--a {% ifequal active_tab "user" %}class="active" {% endifequal %}href="{% url wiki_user %}">{% trans "User" %}</a>
+        <a {% ifequal active_tab "all" %}class="active" {% endifequal %}href="{% url wiki_document_list %}">{% trans "All" %}</a>
+        <a {% ifequal active_tab "create" %}class="active" {% endifequal %}href="{% url wiki_create_missing %}">{% trans "Add" %}</a>
+        <a {% ifequal active_tab "upload" %}class="active" {% endifequal %}href="{% url wiki_upload %}">{% trans "Upload" %}</a-->
+    </div>
+
+    <span id="login-box">
+        {% include "registration/head_login.html" %}
+    </span>
+
+    <div class='clr' ></div>
+</div>
+
+<div id="content">
 
-{% block maincontent %}
-<h1><img src="{{ STATIC_URL }}img/logo.png">{% trans "Platforma Redakcyjna" %}</h1>
 <div id="wiki_layout_left_column">
        {% block leftcolumn %}
        {% endblock leftcolumn %}
        {% block rightcolumn %}
        {% endblock rightcolumn %}
 </div>
-{% endblock maincontent %}
\ No newline at end of file
+
+</div>
+
+{% compressed_js 'listing' %}
+{% block extrabody %}
+{% endblock %}
+</body>
+</html>
index f4b15a9..347b7a3 100755 (executable)
@@ -45,6 +45,9 @@
         </td>
         <td><a href="{% url wiki_chunk_edit book.slug c.chunk.slug%}">[{% trans "edit" %}]</a></td>
         <td>{% if c.chunk.publishable %}P{% endif %}</td>
+        <td>{% if c.chunk.user.is_authenticated %}
+                <a href="{% url wiki_user c.chunk.user.username %}">{{ c.chunk.user }}</a>
+            {% endif %}</td>
         <td><a href="{% url wiki_chunk_add book.slug c.chunk.slug %}">[+]</a></td>
         </tr>
     {% endfor %}
diff --git a/apps/wiki/templates/wiki/chunk_list_item.html b/apps/wiki/templates/wiki/chunk_list_item.html
new file mode 100755 (executable)
index 0000000..bec9e75
--- /dev/null
@@ -0,0 +1,9 @@
+<tr>
+    <td colspan="3">
+        <a target="_blank"
+        href="{% url wiki_chunk_edit chunk.book.slug chunk.slug %}">[?]</a>
+        <a target="_blank"
+                href="{{ chunk.get_absolute_url }}">
+                {{ chunk.pretty_name }}</a>
+    </td>
+</tr>
index 25b4cf2..221b63d 100644 (file)
@@ -1,6 +1,7 @@
 {% extends "wiki/base.html" %}
 
 {% load i18n %}
+{% load pagination_tags %}
 
 {% block extrabody %}
 {{ block.super }}
@@ -22,7 +23,7 @@ $(function() {
 
 {% block leftcolumn %}
        <form method="get" action="#">
-    <table  id="file-list">
+    <table id="file-list">
        <thead>
                <tr><th>Filtr:</th>
                        <th><input autocomplete="off" name="filter" id="file-list-filter" type="text" size="40" /></th>
@@ -30,10 +31,12 @@ $(function() {
                        </tr>
                </thead>
                <tbody>
+        {% autopaginate books 20 %}
        {% for book in books %}
             {{ book.list_html }}
        {% endfor %}
                </tbody>
+        <tr><td colspan="3">{% paginate %}</td></tr>
     </table>
        </form>
 {% endblock leftcolumn %}
diff --git a/apps/wiki/templates/wiki/main_tabs.html b/apps/wiki/templates/wiki/main_tabs.html
new file mode 100755 (executable)
index 0000000..82321cc
--- /dev/null
@@ -0,0 +1,3 @@
+{% for tab in tabs %}
+    <a {% ifequal active_tab tab.slug %}class="active" {% endifequal %}href="{{ tab.url }}">{{ tab.caption }}</a>
+{% endfor %}
index cb5bf20..8acf761 100644 (file)
@@ -1,14 +1,40 @@
 from __future__ import absolute_import
 
+from django.core.urlresolvers import reverse
 from django.template.defaultfilters import stringfilter
 from django import template
+from django.utils.translation import ugettext as _
+
 
 register = template.Library()
 
-from wiki.models import split_name
 
-@register.filter
-@stringfilter
-def wiki_title(value):
-    parts = (p.replace('_', ' ').title() for p in split_name(value))
-    return ' / '.join(parts)
+class Tab(object):
+    slug = None
+    caption = None
+    url = None
+
+    def __init__(self, slug, caption, url):
+        self.slug = slug
+        self.caption = caption
+        self.url = url
+
+
+@register.inclusion_tag("wiki/main_tabs.html", takes_context=True)
+def main_tabs(context):
+    active = getattr(context['request'], 'wiki_active_tab', None)
+
+    tabs = []
+    user = context['user']
+    if user.is_authenticated():
+        tabs.append(Tab('my', _('Assigned to me'), reverse("wiki_user")))
+
+    tabs.append(Tab('unassigned', _('Unassigned'), reverse("wiki_unassigned")))
+    tabs.append(Tab('all', _('All'), reverse("wiki_document_list")))
+    tabs.append(Tab('create', _('Add'), reverse("wiki_create_missing")))
+    tabs.append(Tab('upload', _('Upload'), reverse("wiki_upload")))
+
+    if user.is_staff:
+        tabs.append(Tab('admin', _('Admin'), reverse("admin:index")))
+
+    return {"tabs": tabs, "active_tab": active}
index c7da6ef..f0b602c 100644 (file)
@@ -15,6 +15,9 @@ urlpatterns = patterns('wiki.views',
     #url(r'^catalogue/([^/]+)/$', 'document_list'),
     #url(r'^catalogue/([^/]+)/([^/]+)/$', 'document_list'),
     #url(r'^catalogue/([^/]+)/([^/]+)/([^/]+)$', 'document_list'),
+    url(r'^unassigned/$', 'unassigned', name='wiki_unassigned'),
+    url(r'^user/$', 'my', name='wiki_user'),
+    url(r'^user/(?P<username>[^/]+)/$', 'user', name='wiki_user'),
 
     url(r'^edit/(?P<slug>[^/]+)/(?:(?P<chunk>[^/]+)/)?$',
         'editor', name="wiki_editor"),
@@ -27,6 +30,8 @@ urlpatterns = patterns('wiki.views',
 
     url(r'^create/(?P<slug>[^/]*)/',
         'create_missing', name='wiki_create_missing'),
+    url(r'^create/',
+        'create_missing', name='wiki_create_missing'),
 
     url(r'^gallery/(?P<directory>[^/]+)/$',
         'gallery', name="wiki_gallery"),
index a68fa35..0821488 100644 (file)
@@ -7,12 +7,13 @@ from lxml import etree
 
 from django.conf import settings
 
+from django.contrib.auth.models import User
 from django.contrib.auth.decorators import login_required
 from django.views.generic.simple import direct_to_template
 from django.views.decorators.http import require_POST, require_GET
 from django.core.urlresolvers import reverse
 from wiki.helpers import (JSONResponse, JSONFormInvalid, JSONServerError,
-                ajax_require_permission, recursive_groupby)
+                ajax_require_permission, recursive_groupby, active_tab)
 from django import http
 from django.shortcuts import get_object_or_404, redirect
 from django.http import Http404
@@ -41,6 +42,7 @@ import operator
 MAX_LAST_DOCS = 10
 
 
+@active_tab('all')
 @never_cache
 def document_list(request):
     return direct_to_template(request, 'wiki/document_list.html', extra_context={
@@ -50,6 +52,34 @@ def document_list(request):
     })
 
 
+@active_tab('unassigned')
+@never_cache
+def unassigned(request):
+    return direct_to_template(request, 'wiki/document_list.html', extra_context={
+        'books': Chunk.objects.filter(user=None),
+        'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
+                        key=lambda x: x[1]['time'], reverse=True),
+    })
+
+
+@never_cache
+def user(request, username=None):
+    if username is None:
+        if request.user.is_authenticated():
+            user = request.user
+        else:
+            raise Http404
+    else:
+        user = get_object_or_404(User, username=username)
+
+    return direct_to_template(request, 'wiki/document_list.html', extra_context={
+        'books': Chunk.objects.filter(user=user),
+        'last_books': sorted(request.session.get("wiki_last_books", {}).items(),
+                        key=lambda x: x[1]['time'], reverse=True),
+    })
+my = login_required(active_tab('my')(user))
+
+
 @never_cache
 def editor(request, slug, chunk=None, template_name='wiki/document_details.html'):
     try:
@@ -118,7 +148,10 @@ def editor_readonly(request, slug, chunk=None, template_name='wiki/document_deta
     })
 
 
-def create_missing(request, slug):
+@active_tab('create')
+def create_missing(request, slug=None):
+    if slug is None:
+        slug = ''
     slug = slug.replace(' ', '-')
 
     if request.method == "POST":
@@ -148,6 +181,7 @@ def create_missing(request, slug):
     })
 
 
+@active_tab('upload')
 def upload(request):
     if request.method == "POST":
         form = forms.DocumentsUploadForm(request.POST, request.FILES)
index f5e90de..36c54b0 100644 (file)
@@ -81,6 +81,7 @@ MIDDLEWARE_CLASSES = (
     'django_cas.middleware.CASMiddleware',
 
     'django.middleware.doc.XViewMiddleware',
+    'pagination.middleware.PaginationMiddleware',
     'maintenancemode.middleware.MaintenanceModeMiddleware',
 )
 
@@ -117,6 +118,7 @@ INSTALLED_APPS = (
     'south',
     'sorl.thumbnail',
     'filebrowser',
+    'pagination',
 
     'dvcs',
     'wiki',
index f6f55d7..bb6bea7 100644 (file)
@@ -7,28 +7,57 @@
 */
 
 body {
-       background-color: #84BF2A;
+    margin: 0;
+    font-family: verdana, sans-serif;
+    font-size: 12px;
 }
 
-#content {
-    background: #EFEFEF;
-    border: 1px solid black;
-    padding: 0.5em 2em;
-    margin: 1em;
-       overflow: hidden;
+
+.clr {
+    clear: both;
+}
+
+#tabs-nav {
+    padding: 5px 5px 0 10px;
+    background: #ffdfbf;
+    border-bottom: 1px solid #ff8000;
+    position: relative;
+}
+
+#tabs-nav-left {
+    margin-left: 60px;
+}
+
+#tabs-nav-left a {
+    display: block;
+    float: left;
+    padding: 5px 20px 5px 20px;
+    margin-bottom: -1px;
+    border-width: 1px;
+    border-style: solid;
+    border-color: rgba(0,0,0,0);
 }
 
-#content h1 img {
-       vertical-align: middle;
+#tabs-nav-left .active {
+    background: white;
+    border-color: #ff8000 #ff8000 white #ff8000;
 }
 
-#content h1 {
-       border-bottom: 2px solid black;
-       padding: 0.5em;
-       font-size: 2opt;
-       font-family: sans-serif;
+#login-box {
+    float: right;
 }
 
+#logo {
+    position: absolute;
+    bottom: 0;
+}
+
+#content {
+    padding: 10px;
+}
+
+
+
 #file-list {
        overflow: visible;
        float: left;
@@ -58,7 +87,7 @@ body {
 }
 
 a, a:visited, a:active {
-       color: blue;
+       color: #bf6000;
        text-decoration: none;
 }
 
@@ -67,9 +96,6 @@ a:hover {
 }
 
 
-#loading-overlay {
-       display: none;
-}
 
 .error {
     color: red;
diff --git a/redakcja/static/img/wl-orange.png b/redakcja/static/img/wl-orange.png
new file mode 100644 (file)
index 0000000..d5c56d0
Binary files /dev/null and b/redakcja/static/img/wl-orange.png differ
index 6e84562..6ba7739 100644 (file)
@@ -13,6 +13,7 @@ httplib2 # oauth2 dependency
 Django>=1.1.1,<1.2
 sorl-thumbnail>=3.2
 django-maintenancemode>=0.9
+django-pagination
 
 # migrations
 south>=0.6