Merge Request #102

Closed
softwarepublico/colab!102
Created by Matheus de Sousa Faria

Widget System

Closed by Sergio Oliveira

Changes were not merged into target branch

Commits (8)
3 participants
colab/accounts/templates/accounts/user_update_form.html
1 1 {% extends "base.html" %}
2   -{% load i18n gravatar %}
  2 +{% load i18n gravatar plugins %}
3 3  
4 4 {% block head_js %}
5 5 <script>
... ... @@ -102,6 +102,14 @@ $(function() {
102 102 </script>
103 103 {% endblock %}
104 104  
  105 +{% block head %}
  106 + {{ block.super }}
  107 +
  108 + {% for widget in widgets %}
  109 + {{ widget.get_header }}
  110 + {% endfor %}
  111 +
  112 +{% endblock %}
105 113  
106 114 {% block main-content %}
107 115  
... ... @@ -118,83 +126,115 @@ $(function() {
118 126 <br>
119 127 <br>
120 128  
121   - <form method="post">
122   - {% csrf_token %}
123   -
124   - <div class="row">
125   - <div class="col-lg-8 col-md-7 col-sm-12 col-xm-12">
126   - {% for field in form %}
127   - <div class="col-lg-6 col-md-6 col-sm-12 col-xm-12">
128   - <div class="form-group{% if field.field.required %} required{% endif %}{% if field.errors %} alert alert-danger has-error{% endif %}">
129   - <label for="{{ field.name }}" class="control-label">
130   - {{ field.label }}
131   - </label>
132   - {{ field }}
133   - {{ field.errors }}
134   - </div>
135   - </div>
136   - {% endfor %}
137   - </div>
  129 + <!-- Start of navs -->
  130 + <ul class="nav nav-tabs">
  131 + <li class="active"><a data-toggle="pill" href="#profile">Profile</a></li>
  132 + {% for widget in widgets %}
  133 + <li>
  134 + <a data-toggle="pill" href="#{{ widget.identifier }}">{{ widget.name }}</a>
  135 + </li>
  136 + {% endfor %}
  137 + </ul>
  138 + <!-- End of navs -->
  139 + <style type="text/css">
  140 + .tab-pane{
  141 + border: 1px solid #DDD;
  142 + border-top: none;
  143 + padding: 10px;
  144 + }
138 145  
139   - <div class="col-lg-4 col-md-5 col-sm-12 col-xm-12">
140   - <div class="panel panel-default">
141   - <div class="panel-heading">
142   - <h3 class="panel-title">{% trans "Emails" %}</h3>
143   - </div>
144   - <div class="panel-body">
145   - <ul class="unstyled-list emails">
146   - {% for email in user_.emails.iterator %}
147   - <li>
148   - {% gravatar user_.email 30 %}
149   - <span class="email-address">{{ email.address }}</span>
150   - {% if email.address == user_.email %}
151   - <span class="label label-success">{% trans "Primary" %}</span>
152   - {% else %}
153   - <div class="text-right">
154   - <button class="btn btn-default set-primary" data-loading-text="{% trans 'Setting...' %}">{% trans "Set as Primary" %}</button>
155   - <button class="btn btn-danger delete-email" data-loading-text="{% trans 'Deleting...' %}">{% trans "Delete" %}</button>
156   - </div>
157   - {% endif %}
158   - <hr />
159   - </li>
  146 + .nav-tabs a:focus {
  147 + outline: none;
  148 + }
  149 + </style>
  150 + <div class="tab-content">
  151 + <div id="profile" class="tab-pane fade in active">
  152 + <form method="post">
  153 + {% csrf_token %}
  154 +
  155 + <div class="row">
  156 + <div class="col-lg-8 col-md-7 col-sm-12 col-xm-12">
  157 + {% for field in form %}
  158 + <div class="col-lg-6 col-md-6 col-sm-12 col-xm-12">
  159 + <div class="form-group{% if field.field.required %} required{% endif %}{% if field.errors %} alert alert-danger has-error{% endif %}">
  160 + <label for="{{ field.name }}" class="control-label">
  161 + {{ field.label }}
  162 + </label>
  163 + {{ field }}
  164 + {{ field.errors }}
  165 + </div>
  166 + </div>
160 167 {% endfor %}
161   - {% for email in user_.emails_not_validated.iterator %}
162   - <li>
163   - {% gravatar user_.email 30 %}
164   - <span class="email-address">{{ email.address }}</span>
165   - <div class="text-right">
166   - <button class="btn btn-default verify-email" data-loading-text="{% trans 'Sending verification...' %}"><span class="icon-warning-sign"></span> {% trans "Verify" %}</button>
167   - <button class="btn btn-danger delete-email">{% trans "Delete" %}</button>
  168 + </div>
  169 +
  170 + <div class="col-lg-4 col-md-5 col-sm-12 col-xm-12">
  171 + <div class="panel panel-default">
  172 + <div class="panel-heading">
  173 + <h3 class="panel-title">{% trans "Emails" %}</h3>
  174 + </div>
  175 + <div class="panel-body">
  176 + <ul class="unstyled-list emails">
  177 + {% for email in user_.emails.iterator %}
  178 + <li>
  179 + {% gravatar user_.email 30 %}
  180 + <span class="email-address">{{ email.address }}</span>
  181 + {% if email.address == user_.email %}
  182 + <span class="label label-success">{% trans "Primary" %}</span>
  183 + {% else %}
  184 + <div class="text-right">
  185 + <button class="btn btn-default set-primary" data-loading-text="{% trans 'Setting...' %}">{% trans "Set as Primary" %}</button>
  186 + <button class="btn btn-danger delete-email" data-loading-text="{% trans 'Deleting...' %}">{% trans "Delete" %}</button>
  187 + </div>
  188 + {% endif %}
  189 + <hr />
  190 + </li>
  191 + {% endfor %}
  192 + {% for email in user_.emails_not_validated.iterator %}
  193 + <li>
  194 + {% gravatar user_.email 30 %}
  195 + <span class="email-address">{{ email.address }}</span>
  196 + <div class="text-right">
  197 + <button class="btn btn-default verify-email" data-loading-text="{% trans 'Sending verification...' %}"><span class="icon-warning-sign"></span> {% trans "Verify" %}</button>
  198 + <button class="btn btn-danger delete-email">{% trans "Delete" %}</button>
  199 + </div>
  200 + <hr />
  201 + </li>
  202 + {% endfor %}
  203 + </ul>
  204 + <div class="form-group">
  205 + <label for="new_email">{% trans "Add another email address:" %}</label>
  206 + <input id="new_email" name="new_email" class="form-control" autocomplete="off" />
168 207 </div>
169   - <hr />
170   - </li>
171   - {% endfor %}
172   - </ul>
173   - <div class="form-group">
174   - <label for="new_email">{% trans "Add another email address:" %}</label>
175   - <input id="new_email" name="new_email" class="form-control" autocomplete="off" />
  208 + <button class="btn btn-primary pull-right" id="add-email">{% trans "Add" %}</button>
  209 + </div>
176 210 </div>
177   - <button class="btn btn-primary pull-right" id="add-email">{% trans "Add" %}</button>
178 211 </div>
179   - </div>
180   - </div>
181   - <div class="col-lg-4 col-md-5 col-sm-12 col-xm-12">
182   - <div class="panel panel-default">
183   - <div class="panel-heading">
184   - <h3 class="panel-title">
185   - {% trans 'Change Password' %}
186   - </h3>
  212 + <div class="col-lg-4 col-md-5 col-sm-12 col-xm-12">
  213 + <div class="panel panel-default">
  214 + <div class="panel-heading">
  215 + <h3 class="panel-title">
  216 + {% trans 'Change Password' %}
  217 + </h3>
  218 + </div>
  219 + <div class="panel-body">
  220 + <a href="{% url 'password_change' %}" class="btn btn-default btn-primary pull-right btn-block">{% trans "Change Password" %}</a>
  221 + </div>
  222 + </div>
187 223 </div>
188   - <div class="panel-body">
189   - <a href="{% url 'password_change' %}" class="btn btn-default btn-primary pull-right btn-block">{% trans "Change Password" %}</a>
  224 + </div>
  225 + <div class="row">
  226 + <div class="submit">
  227 + <button type="submit" class="btn btn-primary btn-lg btn-block">{% trans "Update" %}</button>
190 228 </div>
191 229 </div>
192   - </div>
  230 + </form>
193 231 </div>
194   - <div class="row">
195   - <div class="submit">
196   - <button type="submit" class="btn btn-primary btn-lg btn-block">{% trans "Update" %}</button>
  232 + {% for widget in widgets %}
  233 + <div id="{{ widget.identifier }}" class="tab-pane fade">
  234 + <h2>{{ widget.name }}</h2>
  235 + {{ widget.get_body }}
197 236 </div>
198   - </div>
199   - </form>
  237 + {% endfor %}
  238 + </div>
  239 +
200 240 {% endblock %}
... ...
colab/accounts/views.py
... ... @@ -15,6 +15,7 @@ from colab.super_archives.models import (EmailAddress,
15 15 EmailAddressValidation)
16 16 from colab.search.utils import get_collaboration_data, get_visible_threads
17 17 from colab.accounts.models import User
  18 +from colab.widgets.widget_manager import WidgetManager
18 19  
19 20 from .forms import (UserCreationForm, ListsForm, UserUpdateForm)
20 21 from .utils import mailman
... ... @@ -42,6 +43,12 @@ class UserProfileUpdateView(UserProfileBaseMixin, UpdateView):
42 43  
43 44 return obj
44 45  
  46 + def get_context_data(self, **kwargs):
  47 + context = {}
  48 + context['widgets'] = WidgetManager.get_widgets('profile', self.request)
  49 + context.update(kwargs)
  50 + return super(UserProfileUpdateView, self).get_context_data(**context)
  51 +
45 52  
46 53 class UserProfileDetailView(UserProfileBaseMixin, DetailView):
47 54 template_name = 'accounts/user_detail.html'
... ...
colab/plugins/gitlab/__init__.py 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +
  2 +
  3 +default_app_config = 'colab.plugins.gitlab.apps.ProxyGitlabAppConfig'
  4 +
  5 +from colab.plugins.utils.widget_manager import WidgetManager
  6 +from colab.plugins.gitlab.widgets import GitlabProfileWidget
  7 +
  8 +WidgetManager.register_widget('profile', GitlabProfileWidget())
... ...
colab/plugins/gitlab/profile/diazo.xml 0 → 100644
... ... @@ -0,0 +1,36 @@
  1 +<rules
  2 + xmlns="http://namespaces.plone.org/diazo"
  3 + xmlns:css="http://namespaces.plone.org/diazo/css"
  4 + xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  5 +
  6 + <xsl:variable name="username" select="str:replace(//a/@href[(contains(., '/gitlab/u/'))], '/gitlab/u/', '')" />
  7 + <xsl:template match="a/@href[(contains(., '/profile/'))]">
  8 + <xsl:attribute name="href">/account/<xsl:value-of select="$username"/>/edit?code=<xsl:value-of select="."/>/</xsl:attribute>
  9 + </xsl:template>
  10 + <xsl:template match="form/@action[(contains(., '/gitlab/'))]">
  11 + <xsl:attribute name="action">/account/<xsl:value-of select="$username"/>/edit?code=<xsl:value-of select="."/>/</xsl:attribute>
  12 + </xsl:template>
  13 +
  14 + <!-- Drop colab stuff -->
  15 + <drop css:theme="nav.navbar-fixed-top" />
  16 + <drop css:theme="div.footer" />
  17 +
  18 + <!-- Drop unused tabs on gitlab -->
  19 + <drop content="div[@class='container']/ul/li[1]" />
  20 + <drop content="div[@class='container']/ul/li[3]" />
  21 + <drop content="div[@class='container']/ul/li[4]" />
  22 + <drop content="div[@class='container']/ul/li[7]" />
  23 + <drop content="fieldset[@class='update-username']" />
  24 +
  25 + <before theme-children="/html/head" content-children="/html/head" />
  26 + <before css:theme-children="#main-content" css:content-children="body" />
  27 +
  28 + <merge attributes="class" css:theme="body" css:content="body" />
  29 +
  30 + <!-- Add gitlab properties -->
  31 + <merge attributes="data-page" css:theme="body" css:content="body" />
  32 + <merge attributes="data-project-id" css:theme="body" css:content="body" />
  33 +
  34 + <drop css:content="#top-panel" />
  35 + <drop css:content=".navbar-gitlab" />
  36 +</rules>
... ...
colab/plugins/gitlab/templates/proxy/gitlab_profile.html 0 → 100644
... ... @@ -0,0 +1,49 @@
  1 +
  2 +{% load static from staticfiles %}
  3 +
  4 +{% block head_css %}
  5 +<style>
  6 + /* Reset left and with for .modal-dialog style (like gitlab does),
  7 + the bootstrap.css one makes it small */
  8 + @media screen and (min-width: 768px) {
  9 + .modal-dialog {
  10 + left: auto;
  11 + width: auto;
  12 + }
  13 + }
  14 +
  15 + div#main-content div.container {
  16 + width: 1110px;
  17 + }
  18 + div#main-content div.flash-container{
  19 + width: 85%;
  20 + }
  21 + #breadcrumbs {
  22 + border: 0 !important;
  23 + }
  24 +
  25 + #right-top-nav {
  26 + margin-right: 5em !important;
  27 + }
  28 +</style>
  29 +{% endblock %}
  30 +
  31 +{% block head_js %}
  32 +<script>jQuery.noConflict();</script>
  33 +{% endblock %}
  34 +
  35 +<div id="main-content"></div>
  36 +<script type="text/javascript">
  37 + jQuery(function(){
  38 + // bootstrap.css forces .hide {display:none!important}, and this makes
  39 + // gitlab .hide elements NEVER have a display:block, so
  40 + // instead of editing bootstrap.css, we just removed '.hide' css class and
  41 + // toggled
  42 + jQuery('.hide').removeClass('hide').css('display', 'none');
  43 + });
  44 +
  45 + // Extremelly ugly solutions to add Colab's CSRF token to Gitlab forms
  46 + // add CSRF token to HTTP header***
  47 + jQuery('div#code form').append(jQuery("input[name='csrfmiddlewaretoken']"));
  48 + $('a.delete-key').attr('data-method', 'delete\" type=\"hidden\" /> <input name=\"csrfmiddlewaretoken\" value=\"' + jQuery("input[name='csrfmiddlewaretoken']").val());
  49 +</script>
... ...
colab/plugins/gitlab/urls.py 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +
  2 +from django.conf.urls import patterns, url
  3 +
  4 +from .views import GitlabProxyView, GitlabProfileProxyView
  5 +
  6 +urlpatterns = patterns('',
  7 + url(r'^(?P<path>.*)$', GitlabProxyView.as_view(), name='gitlab'),
  8 +)
... ...
colab/plugins/gitlab/views.py 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +
  2 +from ..utils.views import ColabProxyView
  3 +import os, sys
  4 +
  5 +
  6 +class GitlabProxyView(ColabProxyView):
  7 + app_label = 'gitlab'
  8 + diazo_theme_template = 'proxy/gitlab.html'
  9 +
  10 +
  11 +class GitlabProfileProxyView(ColabProxyView):
  12 + app_label = 'gitlab'
  13 + diazo_theme_template = 'proxy/gitlab_profile.html'
  14 +
  15 + @property
  16 + def diazo_rules(self):
  17 + child_class_file = sys.modules[self.__module__].__file__
  18 + app_path = os.path.abspath(os.path.dirname(child_class_file))
  19 + diazo_path = os.path.join(app_path, 'profile/diazo.xml')
  20 +
  21 + self.log.debug("diazo_rules: %s", diazo_path)
  22 + return diazo_path
... ...
colab/plugins/gitlab/widgets.py 0 → 100644
... ... @@ -0,0 +1,35 @@
  1 +from colab.plugins.gitlab.views import GitlabProxyView, GitlabProfileProxyView
  2 +from colab.plugins.utils.widget_manager import Widget
  3 +from django.utils.safestring import mark_safe
  4 +
  5 +
  6 +class GitlabProfileWidget(GitlabProxyView, Widget):
  7 + identifier = 'code'
  8 + name = 'Gitlab Profile'
  9 + default_url = '/gitlab/profile/account'
  10 +
  11 + def get_body(self):
  12 + start = self.content.find('<body')
  13 + start = self.content.find('>', start)
  14 + end = self.content.find('</body>')
  15 + print "start = " + str(start) + ", end = " + str(end)
  16 + print "content = " + self.content
  17 +
  18 + if -1 in [start, end]:
  19 + return ''
  20 +
  21 + body = self.content[start + len('>'):end]
  22 + return mark_safe(body)
  23 +
  24 + def generate_content(self, request):
  25 + requested_url = request.GET.get('code', self.default_url)
  26 + g = GitlabProfileProxyView()
  27 + r = g.dispatch(request, requested_url)
  28 +
  29 + if r.status_code == 302:
  30 + location = r.get('Location')
  31 + requested_url = location[location.find('/{}/'.format(self.app_label)):]
  32 + request.method = 'GET'
  33 + r = g.dispatch(request, requested_url)
  34 +
  35 + self.content = r.content
... ...
colab/settings.py
... ... @@ -293,3 +293,5 @@ TEMPLATE_DIRS += (
293 293 )
294 294  
295 295 conf.validate_database(DATABASES, DEFAULT_DATABASE, DEBUG)
  296 +
  297 +conf.load_widgets_settings()
... ...
colab/urls.py
... ... @@ -3,7 +3,7 @@ from django.conf import settings
3 3 from django.views.generic import TemplateView
4 4 from django.contrib import admin
5 5 from django.views.generic import RedirectView
6   -
  6 +from accounts.views import UserProfileUpdateView
7 7  
8 8 admin.autodiscover()
9 9  
... ...
colab/utils/conf.py
... ... @@ -137,6 +137,43 @@ def load_colab_apps():
137 137 return {'COLAB_APPS': COLAB_APPS}
138 138  
139 139  
  140 +def load_widgets_settings():
  141 + settings_file = os.getenv('COLAB_WIDGETS_SETTINGS',
  142 + '/etc/colab/widgets_settings.py')
  143 + settings_module = settings_file.split('.')[-2].split('/')[-1]
  144 + py_path = "/".join(settings_file.split('/')[:-1])
  145 + logger.info('Widgets Settings file: %s', settings_file)
  146 +
  147 + if not os.path.exists(py_path):
  148 + return
  149 +
  150 + original_path = sys.path
  151 + sys.path.append(py_path)
  152 + importlib.import_module(settings_module)
  153 +
  154 + # Read settings from widgets.d
  155 + settings_dir = os.getenv('COLAB_WIDGETS',
  156 + '/etc/colab/widgets.d')
  157 + logger.info('Widgets Settings directory: %s', settings_dir)
  158 + sys.path = original_path
  159 +
  160 + if not os.path.exists(settings_dir):
  161 + return
  162 +
  163 + for file_name in os.listdir(settings_dir):
  164 + if not file_name.endswith('.py'):
  165 + continue
  166 +
  167 + original_path = sys.path
  168 + sys.path.append(settings_dir)
  169 +
  170 + file_module = file_name.split('.')[0]
  171 + importlib.import_module(file_module)
  172 + logger.info('Loaded %s/%s', settings_dir, file_name)
  173 +
  174 + sys.path = original_path
  175 +
  176 +
140 177 def validate_database(database_dict, default_db, debug):
141 178 db_name = database_dict.get('default', {}).get('NAME')
142 179 if not debug and db_name == default_db:
... ...
colab/widgets/__init__.py 0 → 100644
colab/widgets/admin.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +from django.contrib import admin
  2 +
  3 +# Register your models here.
... ...
colab/widgets/migrations/__init__.py 0 → 100644
colab/widgets/models.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +from django.db import models
  2 +
  3 +# Create your models here.
... ...
colab/widgets/tests/__init__.py 0 → 100644
colab/widgets/tests/test_widget_manager.py 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +from django.test import TestCase
  2 +
  3 +from colab.widgets.widget_manager import WidgetManager, Widget
  4 +
  5 +class WidgetManagerTest(TestCase):
  6 +
  7 + def test_add_widgets_to_key_area(self):
  8 + area = 'profile'
  9 + WidgetManager.register_widget(area, Widget())
  10 +
  11 + self.assertEqual(len(WidgetManager.get_widgets(area)), 1)
  12 +
... ...
colab/widgets/views.py 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +from django.shortcuts import render
  2 +
  3 +# Create your views here.
... ...
colab/widgets/widget_manager.py 0 → 100644
... ... @@ -0,0 +1,53 @@
  1 +from django.utils.safestring import mark_safe
  2 +
  3 +class Widget(object):
  4 + identifier = None
  5 + name = None
  6 + default_url = None
  7 + content = ''
  8 +
  9 + def get_body(self):
  10 + # avoiding regex in favor of performance
  11 + start = self.content.find('<body>')
  12 + end = self.content.find('</body>')
  13 +
  14 + if -1 in [start, end]:
  15 + return ''
  16 +
  17 + body = self.content[start + len('<body>'):end]
  18 + return mark_safe(body)
  19 +
  20 + def get_header(self):
  21 + # avoiding regex in favor of performance
  22 + start = self.content.find('<head>')
  23 + end = self.content.find('</head>')
  24 +
  25 + if -1 in [start, end]:
  26 + return ''
  27 +
  28 + head = self.content[start + len('<head>'):end]
  29 + return mark_safe(head)
  30 +
  31 + def generate_content(self, request=None):
  32 + self.content = ''
  33 +
  34 +
  35 +class WidgetManager(object):
  36 + widget_categories = {}
  37 +
  38 + @staticmethod
  39 + def register_widget(category, widget):
  40 + if not WidgetManager.widget_categories.has_key(category):
  41 + WidgetManager.widget_categories[category] = []
  42 +
  43 + WidgetManager.widget_categories[category].append(widget)
  44 +
  45 + @staticmethod
  46 + def get_widgets(category, request=None):
  47 + if not WidgetManager.widget_categories.has_key(category):
  48 + return []
  49 +
  50 + widgets = WidgetManager.widget_categories[category]
  51 + for widget in widgets:
  52 + widget.generate_content(request)
  53 + return widgets
... ...
diagrama_classes.asta 0 → 100644
No preview for this file type
tests/run.py
... ... @@ -5,7 +5,9 @@ import sys
5 5  
6 6 os.environ['DJANGO_SETTINGS_MODULE'] = 'colab.settings'
7 7 os.environ['COLAB_SETTINGS'] = 'tests/colab_settings.py'
  8 +os.environ['COLAB_WIDGETS_SETTINGS'] = 'tests/widgets_settings.py'
8 9 os.environ['COLAB_PLUGINS'] = 'tests/plugins.d'
  10 +os.environ['COLAB_WIDGETS'] = 'tests/widgets.d'
9 11 os.environ['COVERAGE_PROCESS_START'] = '.coveragerc'
10 12  
11 13  
... ...
tests/widgets_settings.py 0 → 100644