Merge branch 'master' into lqc-trunk
authorLukasz Rekucki <lrekucki@gmail.com>
Sun, 8 Nov 2009 13:49:11 +0000 (14:49 +0100)
committerLukasz Rekucki <lrekucki@gmail.com>
Sun, 8 Nov 2009 13:49:11 +0000 (14:49 +0100)
1  2 
apps/api/handlers/library_handlers.py
apps/explorer/views.py
platforma/static/js/models.js
platforma/static/js/views/gallery.js
platforma/templates/explorer/editor.html
platforma/urls.py

@@@ -4,12 -4,8 +4,8 @@@ import os.pat
  import logging
  log = logging.getLogger('platforma.api.library')
  
- __author__= "Łukasz Rekucki"
- __date__ = "$2009-09-25 15:49:50$"
- __doc__ = "Module documentation."
  from piston.handler import BaseHandler, AnonymousBaseHandler
- from django.http import HttpResponse
+ from piston.utils import rc
  
  from datetime import date
  
@@@ -19,10 -15,8 +15,8 @@@ from django.db import IntegrityErro
  import librarian
  import librarian.html
  import difflib
- from librarian import dcparser, parser
++import wlrepo 
  
- import wlrepo
- from api.models import PullRequest
 -from wlrepo import *
  from explorer.models import GalleryForDocument
  
  # internal imports
@@@ -157,7 -151,7 +151,7 @@@ class LibraryHandler(BaseHandler)
                      import traceback
                      # rollback branch creation
                      lib._rollback()
 -                    raise LibraryException(traceback.format_exc())
 +                    raise wlrepo.LibraryException(traceback.format_exc())
  
                  url = reverse('document_view', args=[doc.id])
  
                      url = url )            
              finally:
                  lock.release()
 -        except LibraryException, e:
 +        except wlrepo.LibraryException, e:
              import traceback
              return response.InternalError().django_response({
                  "reason": traceback.format_exc()
              })
 -        except DocumentAlreadyExists:
 +        except wlrepo.DocumentAlreadyExists:
              # Document is already there
              return response.EntityConflict().django_response({
                  "reason": "already-exists",
  #
  # Document Handlers
  #
- class BasicDocumentHandler(AnonymousBaseHandler):
-     allowed_methods = ('GET',)
-     @hglibrary
-     def read(self, request, docid, lib):
-         try:    
-             doc = lib.document(docid)
-         except wlrepo.RevisionNotFound:
-             return rc.NOT_FOUND
-         result = {
-             'name': doc.id,
-             'html_url': reverse('dochtml_view', args=[doc.id]),
-             'text_url': reverse('doctext_view', args=[doc.id]),
-             'dc_url': reverse('docdc_view', args=[doc.id]),
-             'public_revision': doc.revision,
-         }
-         return result
 +
  class DiffHandler(BaseHandler):
      allowed_methods = ('GET',)
      
  #
  class DocumentHandler(BaseHandler):
      allowed_methods = ('GET', 'PUT')
-     anonymous = BasicDocumentHandler
  
      @validate_form(forms.DocumentRetrieveForm, 'GET')
      @hglibrary
              
          try:
              doc = lib.document(docid, user, rev=rev)
 -        except RevisionMismatch, e:
 +        except wlrepo.RevisionMismatch, e:
              # the document exists, but the revision is bad
              return response.EntityNotFound().django_response({
                  'reason': 'revision-mismatch',
                  'docid': docid,
                  'user': user,
              })
 -        except RevisionNotFound, e:
 +        except wlrepo.RevisionNotFound, e:
              # the user doesn't have this document checked out
              # or some other weird error occured
              # try to do the checkout
                          'docid': docid,
                          'user': user,
                      })
 -            except RevisionNotFound, e:
 +            except wlrepo.RevisionNotFound, e:
                  return response.EntityNotFound().django_response({
                      'reason': 'document-not-found',
                      'message': e.message,
@@@ -341,7 -313,7 +314,7 @@@ class DocumentHTMLHandler(BaseHandler)
                      "with-paths": 'boolean(1)',                    
                  })
                  
 -        except (EntryNotFound, RevisionNotFound), e:
 +        except (wlrepo.EntryNotFound, wlrepo.RevisionNotFound), e:
              return response.EntityNotFound().django_response({
                  'reason': 'not-found', 'message': e.message})
          except librarian.ValidationError, e:
  #
  
  class DocumentGalleryHandler(BaseHandler):
-     allowed_methods = ('GET')
+     allowed_methods = ('GET', 'POST')
      
      
      def read(self, request, docid):
  
          return galleries
  
+     def create(self, request, docid):
+         if not request.user.is_superuser:
+             return rc.FORBIDDEN
+         
+         new_path = request.POST.get('path')
+         
+         if new_path:
+             gallery, created = GalleryForDocument.objects.get_or_create(
+                 document = docid,
+                 defaults = {
+                     'subpath': new_path,
+                 }
+             )
+             if not created:
+                 gallery.subpath = new_path
+                 gallery.save()
+             return rc.CREATED
+         
+         return rc.BAD_REQUEST
  
  #
  # Dublin Core handlers
diff --combined apps/explorer/views.py
index fd99f27,5ec452a..a67d27c
mode 100755,100644..100755
@@@ -11,12 -11,12 +11,14 @@@ from django.core.urlresolvers import re
  from django.http import HttpResponse
  from django.utils import simplejson as json
  from django.views.generic.simple import direct_to_template
 +from django.shortcuts import render_to_response
 +from django.template import RequestContext
  from django.contrib.auth.decorators import login_required
  
+ from explorer import forms
  from api.models import PullRequest
  
  def ajax_login_required(view):
      """Similar ro @login_required, but instead of redirect, 
      just return some JSON stuff with error."""
  @login_required
  def display_editor(request, path):
      user = request.GET.get('user', request.user.username)
-     log.info(user)
+     gallery_form = forms.GalleryChoiceForm()
+     
 -    return direct_to_template(request, 'explorer/editor.html', extra_context={
 +    return render_to_response('explorer/editor.html',
-         mimetype="text/html",
++        # mimetype="text/html",
 +        dictionary = {
              'fileid': path,
-             'euser': user
+             'euser': user,
 -            'gallery_form': gallery_form,
 -    })
 -    
++            'gallery_from': gallery_form,
 +        }, context_instance=RequestContext(request))
-     
++
  #
  # View all files
  #
@@@ -120,11 -119,3 +123,11 @@@ def pull_requests(request)
  
      return direct_to_template(request, 'manager/pull_request.html', 
          extra_context = {'objects': objects} )
-         extra_context = {} )
 +
 +
 +#
 +# Testing
 +#
 +def renderer_test(request):
 +    return direct_to_template(request, 'renderer.html', mimetype="application/xhtml+xml",
++        extra_context = {} )
@@@ -36,192 -36,87 +36,192 @@@ Editor.ToolbarButtonsModel = Editor.Mod
  });
  
  
 -// Stany modelu:
  //
 -//                  -> error -> loading
 -//                 /
 -// empty -> loading -> synced -> unsynced -> loading
 -//                           \
 -//                            -> dirty -> updating -> updated -> synced
 +// HTML Document Model
  //
 -Editor.XMLModel = Editor.Model.extend({
 -    _className: 'Editor.XMLModel',
 -    serverURL: null,
 -    data: '',
 +Editor.HTMLModel = Editor.Model.extend({
 +    _className: 'Editor.HTMLModel',
 +    textURL: null,    
      state: 'empty',
 -  
 -    init: function(document, serverURL) {
 +
 +    init: function(document, textURL) {
          this._super();
          this.set('state', 'empty');
          this.set('revision', document.get('revision'));
          this.document = document;
 -        this.serverURL = serverURL;
 -        this.toolbarButtonsModel = new Editor.ToolbarButtonsModel();
 +
 +        this.textURL = textURL;        
 +
 +        this.htmlXSL = null;
 +        this.wlmlXSL = null;
 +        this.rawText = null;
 +
 +        // create a parser and a serializer
 +        this.parser = new DOMParser();
 +        this.serializer = new XMLSerializer();
 +
          this.addObserver(this, 'data', this.dataChanged.bind(this));
      },
 -  
 +
      load: function(force) {
          if (force || this.get('state') == 'empty') {
              this.set('state', 'loading');
 -            messageCenter.addMessage('info', 'xmlload', 'Wczytuję XML...');
 +            messageCenter.addMessage('info', 'xmlload', 'Wczytuję HTML...');
 +
 +            // request all stylesheets
              $.ajax({
 -                url: this.serverURL,
 +                url: documentInfo.staticURL + 'xsl/wl2html_client.xsl',
 +                dataType: 'xml',                
 +                success: this.htmlXSLLoadSuccess.bind(this),
 +                error: this.loadingFailed.bind(this)
 +            });
 +
 +            $.ajax({
 +                url: documentInfo.staticURL + 'xsl/html2wl_client.xsl',
 +                dataType: 'xml',
 +                success: this.wlmlXSLLoadSuccess.bind(this),
 +                error: this.loadingFailed.bind(this)
 +            });
 +
 +            $.ajax({
 +                url: this.textURL,
                  dataType: 'text',
                  data: {
                      revision: this.get('revision'),
                      user: this.document.get('user')
                      },
 -                success: this.loadingSucceeded.bind(this),
 +                success: this.textLoadSuccess.bind(this),
                  error: this.loadingFailed.bind(this)
              });
              return true;
          }
          return false;
      },
 -  
 -    loadingSucceeded: function(data) {
 +
 +    asWLML: function(element) 
 +    {
 +        console.log("Source", element);
 +        var doc = this.parser.parseFromString(this.serializer.serializeToString(element), 'text/xml');
 +
 +        var result = this.wlmlXSL.transformToDocument(doc);
 +
 +        if(!result) {
 +            console.log("Failed", this.wlmlXSL, doc);
 +            throw "Failed to transform fragment";
 +        }
 +        
 +        console.log("Transformed", doc, " to: ", result.documentElement);
 +        return this.serializer.serializeToString(result.documentElement);
 +    },
 +
 +    updateWithWLML: function($element, text)
 +    {
 +        // filter the string
 +        text = text.replace(/\/\s+/g, '<br />');
 +        var chunk = this.parser.parseFromString("<chunk>"+text+"</chunk>", "text/xml");
 +
 +        var errors = $('parsererror', chunk);
 +
 +        // check if chunk is parsable
 +        if(errors.length > 0)
 +            throw {text: errors.text(), html: errors.html()};
 +        
 +        var result = this.htmlXSL.transformToFragment(chunk, document);
 +
 +        console.log("RESULT", this.serializer.serializeToString(result));
 +
 +        if(!result)
 +            throw "WLML->HTML transformation failed.";
 +        
 +        $element.replaceWith(result);
 +        this.set('state', 'dirty');
 +    },
 +
 +    createXSLT: function(xslt_doc) {
 +        var p = new XSLTProcessor();
 +        p.importStylesheet(xslt_doc);
 +        return p;
 +    },
 +
 +    htmlXSLLoadSuccess: function(data) 
 +    {
 +        try {
 +            this.htmlXSL = this.createXSLT(data);
 +
 +            if(this.wlmlXSL && this.htmlXSL && this.rawText)
 +                this.loadSuccess();
 +        } catch(e) {
 +            this.loadingFailed();
 +        }
 +    },
 +
 +    wlmlXSLLoadSuccess: function(data)
 +    {
 +        try {
 +            this.wlmlXSL = this.createXSLT(data);
 +
 +            if(this.wlmlXSL && this.htmlXSL && this.rawText)
 +                this.loadSuccess();
 +        } catch(e) {
 +            this.loadingFailed();
 +        }
 +    },
 +
 +    textLoadSuccess: function(data) {
 +        this.rawText = data;
 +
 +        if(this.wlmlXSL && this.htmlXSL && this.rawText)
 +                this.loadSuccess();
 +    },
 +
 +    loadSuccess: function() {
          if (this.get('state') != 'loading') {
              alert('erroneous state:', this.get('state'));
          }
 -        this.set('data', data);
 +
 +        // prepare text
 +        var doc = null;
 +        doc = this.rawText.replace(/\/\s+/g, '<br />');
 +        doc = this.parser.parseFromString(doc, 'text/xml');
 +        doc = this.htmlXSL.transformToFragment(doc, document).firstChild;
 +
 +        this.set('data', doc);
          this.set('state', 'synced');
 -        messageCenter.addMessage('success', 'xmlload', 'Wczytałem XML :-)');
 +        messageCenter.addMessage('success', 'xmlload', 'Wczytałem HTML :-)');
      },
 -  
 +
      loadingFailed: function(response)
      {
          if (this.get('state') != 'loading') {
              alert('erroneous state:', this.get('state'));
          }
 -        
 +
          var message = parseXHRError(response);
 -        
 +
          this.set('error', '<h2>Błąd przy ładowaniu XML</h2><p>'+message+'</p>');
          this.set('state', 'error');
 -        messageCenter.addMessage('error', 'xmlload', 'Nie udało mi się wczytać XML. Spróbuj ponownie :-(');
 +        messageCenter.addMessage('error', 'xmlload', 'Nie udało mi się wczytać HTML. Spróbuj ponownie :-(');
      },
 -  
 +
      save: function(message) {
          if (this.get('state') == 'dirty') {
 -            this.set('state', 'updating');
 -            messageCenter.addMessage('info', 'xmlsave', 'Zapisuję XML...');
 -      
 +            this.set('state', 'saving');
 +            
 +            messageCenter.addMessage('info', 'htmlsave', 'Zapisuję HTML...');
 +            var wlml = this.asWLML(this.get('data'));
 +
              var payload = {
 -                contents: this.get('data'),
 +                contents: wlml,
                  revision: this.get('revision'),
                  user: this.document.get('user')
              };
 +
              if (message) {
                  payload.message = message;
              }
 -      
 +
              $.ajax({
 -                url: this.serverURL,
 +                url: this.textURL,
                  type: 'post',
                  dataType: 'json',
                  data: payload,
          }
          return false;
      },
 -  
 +
      saveSucceeded: function(data) {
 -        if (this.get('state') != 'updating') {
 +        if (this.get('state') != 'saving') {
              alert('erroneous state:', this.get('state'));
          }
          this.set('revision', data.revision);
          this.set('state', 'updated');
 -        messageCenter.addMessage('success', 'xmlsave', 'Zapisałem XML :-)');
 +        messageCenter.addMessage('success', 'htmlsave', 'Zapisałem :-)');
      },
 -  
 +
      saveFailed: function() {
 -        if (this.get('state') != 'updating') {
 +        if (this.get('state') != 'saving') {
              alert('erroneous state:', this.get('state'));
          }
 -        messageCenter.addMessage('error', 'xmlsave', 'Nie udało mi się zapisać XML. Spróbuj ponownie :-(');
 +        messageCenter.addMessage('error', 'htmlsave', 'Nie udało mi się zapisać.');
          this.set('state', 'dirty');
      },
 -  
 +
      // For debbuging
      set: function(property, value) {
          if (property == 'state') {
          }
          return this._super(property, value);
      },
 -  
 +
      dataChanged: function(property, value) {
          if (this.get('state') == 'synced') {
              this.set('state', 'dirty');
          }
      },
 -  
 +
      dispose: function() {
          this.removeObserver(this);
          this._super();
  });
  
  
 -Editor.HTMLModel = Editor.Model.extend({
 -    _className: 'Editor.HTMLModel',
 -    dataURL: null,
 -    htmlURL: null,
 -    renderURL: null,
 -    displaData: '',
 -    xmlParts: {},
 +// Stany modelu:
 +//
 +//                  -> error -> loading
 +//                 /
 +// empty -> loading -> synced -> unsynced -> loading
 +//                           \
 +//                            -> dirty -> updating -> updated -> synced
 +//
 +Editor.XMLModel = Editor.Model.extend({
 +    _className: 'Editor.XMLModel',
 +    serverURL: null,
 +    data: '',
      state: 'empty',
    
 -    init: function(document, dataURL, htmlURL) {
 +    init: function(document, serverURL) {
          this._super();
          this.set('state', 'empty');
 -        this.set('revision', document.get('revision'));        
 -        
 +        this.set('revision', document.get('revision'));
          this.document = document;
 -        this.htmlURL = htmlURL;
 -        this.dataURL = dataURL;
 -        this.renderURL = documentInfo.renderURL;
 -        this.xmlParts = {};
 +        this.serverURL = serverURL;
 +        this.toolbarButtonsModel = new Editor.ToolbarButtonsModel();
 +        this.addObserver(this, 'data', this.dataChanged.bind(this));
      },
    
      load: function(force) {
          if (force || this.get('state') == 'empty') {
              this.set('state', 'loading');
 -
 -            // load the transformed data
 -            // messageCenter.addMessage('info', 'Wczytuję HTML...');
 -
 +            messageCenter.addMessage('info', 'xmlload', 'Wczytuję XML...');
              $.ajax({
 -                url: this.htmlURL,
 +                url: this.serverURL,
                  dataType: 'text',
                  data: {
                      revision: this.get('revision'),
                  success: this.loadingSucceeded.bind(this),
                  error: this.loadingFailed.bind(this)
              });
 +            return true;
          }
 -    },    
 +        return false;
 +    },
    
      loadingSucceeded: function(data) {
          if (this.get('state') != 'loading') {
          }
          this.set('data', data);
          this.set('state', 'synced');
 +        messageCenter.addMessage('success', 'xmlload', 'Wczytałem XML :-)');
      },
    
 -    loadingFailed: function(response) {
 +    loadingFailed: function(response)
 +    {
          if (this.get('state') != 'loading') {
              alert('erroneous state:', this.get('state'));
          }
          
 -        var err = parseXHRError(response);
 +        var message = parseXHRError(response);
          
 -        this.set('error', '<p>Nie udało się wczytać widoku HTML: </p>' + err.error_message);
 -        this.set('state', 'error');        
 -    },
 -
 -    getXMLPart: function(elem, callback)
 -    {
 -        var path = elem.attr('x-pointer');
 -        if(!this.xmlParts[path])
 -            this.loadXMLPart(elem, callback);
 -        else
 -            callback(path, this.xmlParts[path]);
 -    },
 -
 -    loadXMLPart: function(elem, callback)
 -    {
 -        var path = elem.attr('x-pointer');
 -        var self = this;
 -
 -        $.ajax({
 -            url: this.dataURL,
 -            dataType: 'text',
 -            data: {
 -                revision: this.get('revision'),
 -                user: this.document.get('user'),
 -                chunk: path
 -                // format: 'nl'
 -            },
 -            success: function(data) {
 -                self.xmlParts[path] = data;
 -                console.log(data);
 -                callback(path, data);
 -            },
 -            // TODO: error handling
 -            error: function(data) {
 -                console.log('Failed to load fragment');
 -                callback(undefined, undefined);
 -            }
 -        });
 -    },
 -
 -    putXMLPart: function(elem, data, callback) {
 -        var self = this;
 -      
 -        var path = elem.attr('x-pointer');
 -        this.xmlParts[path] = data;
 -
 -        this.set('state', 'dirty');
 -
 -        /* re-render the changed fragment */
 -        $.ajax({
 -            url: this.renderURL,
 -            type: "POST",
 -            dataType: 'text; charset=utf-8',
 -            data: {
 -                fragment: data,
 -                chunk: path
 -                // format: 'nl'
 -            },
 -            success: function(htmldata) {
 -                callback(elem, htmldata);
 -                self.set('state', 'dirty');
 -            }
 -        });
 +        this.set('error', '<h2>Błąd przy ładowaniu XML</h2><p>'+message+'</p>');
 +        this.set('state', 'error');
 +        messageCenter.addMessage('error', 'xmlload', 'Nie udało mi się wczytać XML. Spróbuj ponownie :-(');
      },
 -
 +  
      save: function(message) {
          if (this.get('state') == 'dirty') {
              this.set('state', 'updating');
 -
 +            messageCenter.addMessage('info', 'xmlsave', 'Zapisuję XML...');
 +      
              var payload = {
 -                chunks: $.toJSON(this.xmlParts),
 +                contents: this.get('data'),
                  revision: this.get('revision'),
                  user: this.document.get('user')
              };
 -
              if (message) {
                  payload.message = message;
              }
 -
 -            console.log(payload)
 -
 +      
              $.ajax({
 -                url: this.dataURL,
 +                url: this.serverURL,
                  type: 'post',
                  dataType: 'json',
                  data: payload,
              return true;
          }
          return false;
 -      
      },
 -
 +  
      saveSucceeded: function(data) {
          if (this.get('state') != 'updating') {
              alert('erroneous state:', this.get('state'));
          }
 -
 -        // flush the cache
 -        this.xmlParts = {};
 -    
          this.set('revision', data.revision);
          this.set('state', 'updated');
 +        messageCenter.addMessage('success', 'xmlsave', 'Zapisałem XML :-)');
      },
 -
 +  
      saveFailed: function() {
          if (this.get('state') != 'updating') {
              alert('erroneous state:', this.get('state'));
 -        }        
 +        }
 +        messageCenter.addMessage('error', 'xmlsave', 'Nie udało mi się zapisać XML. Spróbuj ponownie :-(');
          this.set('state', 'dirty');
      },
 -
 +  
      // For debbuging
      set: function(property, value) {
          if (property == 'state') {
              console.log(this.description(), ':', property, '=', value);
          }
          return this._super(property, value);
 +    },
 +  
 +    dataChanged: function(property, value) {
 +        if (this.get('state') == 'synced') {
 +            this.set('state', 'dirty');
 +        }
 +    },
 +  
 +    dispose: function() {
 +        this.removeObserver(this);
 +        this._super();
      }
  });
  
 -
  Editor.ImageGalleryModel = Editor.Model.extend({
      _className: 'Editor.ImageGalleryModel',
      serverURL: null,
          this.pages = [];
      },
  
+     setGallery: function(path) {
+       $.ajax({
+           url: this.serverURL,
+           type: 'post',
+           data: {
+               path: path,
+           },
+           success: this.settingGallerySucceeded.bind(this)           
+       });
+     },
+     
+     settingGallerySucceeded: function(data) {
+       console.log('settingGallerySucceeded');
+       this.load(true);
+     },
+     
      load: function(force) {
          if (force || this.get('state') == 'empty') {
              console.log("setting state");
@@@ -503,7 -465,7 +519,7 @@@ Editor.DocumentModel = Editor.Model.ext
  
          this.contentModels = {
              'xml': new Editor.XMLModel(this, data.text_url),
 -            'html': new Editor.HTMLModel(this, data.text_url, data.html_url),
 +            'html': new Editor.HTMLModel(this, data.text_url),
              'gallery': new Editor.ImageGalleryModel(this, data.gallery_url)
          };        
  
                  revision: this.get('revision'),
                  user: this.get('user')
              },
 -            complete: this.updateCompleted.bind(this)           
 +            complete: this.updateCompleted.bind(this)           
          });
      },
    
index 31b47ce,a403c9e..d6844c6
mode 100755,100644..100755
@@@ -10,8 -10,7 +10,8 @@@ var ImageGalleryView = View.extend(
    init: function(element, model, parent, template) 
    {    
      console.log("init for gallery");
 -    this._super(element, model, template);
 +    var submodel = model.contentModels['gallery'];
 +    this._super(element, submodel, template);
      this.parent = parent;
  
      console.log("gallery model", this.model);
    },
    
    modelDataChanged: function(property, value) 
-   {    
-     if( property == 'data')
-     {
-         this.render();
-         this.gotoPage(this.currentPage);        
-     }   
+   {   
+     console.log(this.model.get('state'), value, value.length);
+     if ((this.model.get('state') == 'synced') && (value.length == 0)) {
+       console.log('tutaj');
+       this.render('image-gallery-empty-template');
+     } else {
+       this.render();
+       this.gotoPage(this.currentPage);
+     }
    },
  
    gotoPage: function(index) 
    
    modelStateChanged: function(property, value) {   
      if (value == 'loading') {
-       // this.freeze('Ładowanie...');
+       this.freeze('Ładowanie...');
      } else {
+       if ((value == 'synced') && (this.model.get('data').length == 0)) {
+         this.render('image-gallery-empty-template');
+       }
        this.unfreeze();
      }
    },
          mousedown(this.pageDragStart.bind(this));    
    },
  
-   render: function() 
+   render: function(template
    {
-       if(!this.model) return;            
-       
-       /* first unbind all */    
-       if(this.$nextButton) this.$nextButton.unbind();
-       if(this.$prevButton) this.$prevButton.unbind();
-       if(this.$jumpButton) this.$jumpButton.unbind();
-       if(this.$pageInput) this.$pageInput.unbind();
-       if(this.$zoomInButton) this.$zoomInButton.unbind();
-       if(this.$zoomOutButton) this.$zoomOutButton.unbind();
-       if(this.$zoomResetButton) this.$zoomResetButton.unbind();
+     if(!this.model) return;            
+     
+     $('.choose-gallery-button', this.element).unbind();
+     
+     /* first unbind all */    
+     if(this.$nextButton) this.$nextButton.unbind();
+     if(this.$prevButton) this.$prevButton.unbind();
+     if(this.$jumpButton) this.$jumpButton.unbind();
+     if(this.$pageInput) this.$pageInput.unbind();
+     if(this.$zoomInButton) this.$zoomInButton.unbind();
+     if(this.$zoomOutButton) this.$zoomOutButton.unbind();
+     if(this.$zoomResetButton) this.$zoomResetButton.unbind();
+     
+     if (!template) {
        /* render */
        this._super();
+       
        /* fetch important parts */
        this.$pageListRoot = $('.image-gallery-page-list', this.element);
        this.$pages = $('.image-gallery-page-container', this.$pageListRoot);
  
        this.gotoPage(this.currentPage);
        this.changePageZoom(this.pageZoom);
+     } else {
+       this._super(template);
+       
+       var self = this;
+       $('.choose-gallery-button', self.element).click(function() {
+         console.log('CLICK CLICK')
+         self.model.setGallery($('#id_subpath', self.element).val());
+       });
+     }
    },
  
    jumpToPage: function() {     
@@@ -12,8 -12,9 +12,8 @@@
                  docID: '{{ fileid }}',
                  userID: '{{ euser }}',
                  docURL: '{% url document_view fileid %}{% if euser %}?user={{ euser|urlencode }}{% endif %}',
 -                toolbarURL: '{% url toolbar_buttons %}',
 -                renderURL: '{% url api.views.render %}',
 -                              staticURL: '{{ STATIC_URL }}'
 +                toolbarURL: '{% url toolbar_buttons %}',                
 +              staticURL: '{{ STATIC_URL }}'
              }         
                  
        </script>
        <script type="text/html" charset="utf-8" id="html-view-template">
                  <div class="htmlview-toolbar">
                      <a class="html-print-link" href="print" ui:baseref="{% url file_print fileid %}" target="_new">{% trans "Print version"  %}</a>
 +                    <button class="html-add-motive" type="button">Dodaj motyw</button>
 +                    <button class="html-serialize" type="button">Serializuj</button>
                  </div>
                  
                <div class="htmlview">
                </div>
        </script>       
 -        
 -        <script type="text/html" charset="utf-8" id="html-view-frag-menu-template">
 -            <span class="default-menu context-menu">
 -                <span class="edit-button">Edytuj</span>                
 -            </span>
 -            <span class="edit-menu context-menu">
 -                <span class="accept-button">OK</span>
 -                <span class="reject-button">Anuluj</span>
 -            </span>
 -        </script>
 -
 -
  
        <script type="text/html" charset="utf-8" id="flash-view-template">
                <div class="flashview">
          </div>
        </script>
        
+       <script type="text/html" charset="utf-8" id="image-gallery-empty-template">
+           <div class="image-gallery-empty-template">
+             <div class="empty-gallery-info">Galeria dla tego dokumentu nie istnieje i/lub jest pusta.</div>
+             
+             {% if user.is_superuser %}
+             <div class="choose-gallery">
+                 <p>Dołącz galerię do dokumentu:</p>
+                 <p>{{ gallery_form.subpath }} <input class="choose-gallery-button" type="submit" value="Dołącz"></p>
+             </div>
+             {% endif %}
+         </div>
+       </script>
+       
        <script type="text/html" charset="utf-8" id="button-toolbar-view-template">
                <div class="buttontoolbarview panel-toolbar">
                        <div class="buttontoolbarview-tabs toolbar-tabs-container toolbar-buttons-container">
diff --combined platforma/urls.py
index 8a56210,498b5b2..1f7372a
mode 100755,100644..100755
@@@ -13,8 -13,6 +13,8 @@@ urlpatterns = patterns(''
      url(r'^$', 'explorer.views.file_list', name='file_list'),        
      url(r'^file/upload', 'explorer.views.file_upload', name='file_upload'),
  
 +    url(r'^renderer$', 'explorer.views.renderer_test'),
 +
      url(r'^management/pull-requests$', 'explorer.views.pull_requests'),
    
      # Editor panels
      url(r'^file/(?P<docid>[^/]+)/print$', 'explorer.views.print_html', name="file_print"),
  
      # Admin panel
+     (r'^admin/filebrowser/', include('filebrowser.urls')),
      url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
      url(r'^admin/(.*)', admin.site.root),
  
 +
 +
      # Our über-restful api
      url(r'^api/', include('api.urls')),