Commit 0b8f3f6a01e19a023e5b3d62da8a934eceb47016
Exists in
master
and in
4 other branches
Merge pull request #94 from colab/widget_system
Widget system
Showing
18 changed files
with
348 additions
and
71 deletions
Show diff stats
colab/accounts/templates/accounts/user_update_form.html
| 1 | {% extends "base.html" %} | 1 | {% extends "base.html" %} |
| 2 | -{% load i18n gravatar %} | 2 | +{% load i18n gravatar plugins %} |
| 3 | 3 | ||
| 4 | {% block head_js %} | 4 | {% block head_js %} |
| 5 | <script> | 5 | <script> |
| @@ -102,6 +102,14 @@ $(function() { | @@ -102,6 +102,14 @@ $(function() { | ||
| 102 | </script> | 102 | </script> |
| 103 | {% endblock %} | 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 | {% block main-content %} | 114 | {% block main-content %} |
| 107 | 115 | ||
| @@ -118,83 +126,115 @@ $(function() { | @@ -118,83 +126,115 @@ $(function() { | ||
| 118 | <br> | 126 | <br> |
| 119 | <br> | 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 | {% endfor %} | 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 | </div> | 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 | </div> | 210 | </div> |
| 177 | - <button class="btn btn-primary pull-right" id="add-email">{% trans "Add" %}</button> | ||
| 178 | </div> | 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 | </div> | 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 | </div> | 228 | </div> |
| 191 | </div> | 229 | </div> |
| 192 | - </div> | 230 | + </form> |
| 193 | </div> | 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 | </div> | 236 | </div> |
| 198 | - </div> | ||
| 199 | - </form> | 237 | + {% endfor %} |
| 238 | + </div> | ||
| 239 | + | ||
| 200 | {% endblock %} | 240 | {% endblock %} |
colab/accounts/views.py
| @@ -15,6 +15,7 @@ from colab.super_archives.models import (EmailAddress, | @@ -15,6 +15,7 @@ from colab.super_archives.models import (EmailAddress, | ||
| 15 | EmailAddressValidation) | 15 | EmailAddressValidation) |
| 16 | from colab.search.utils import get_collaboration_data, get_visible_threads | 16 | from colab.search.utils import get_collaboration_data, get_visible_threads |
| 17 | from colab.accounts.models import User | 17 | from colab.accounts.models import User |
| 18 | +from colab.widgets.widget_manager import WidgetManager | ||
| 18 | 19 | ||
| 19 | from .forms import (UserCreationForm, ListsForm, UserUpdateForm) | 20 | from .forms import (UserCreationForm, ListsForm, UserUpdateForm) |
| 20 | from .utils import mailman | 21 | from .utils import mailman |
| @@ -42,6 +43,12 @@ class UserProfileUpdateView(UserProfileBaseMixin, UpdateView): | @@ -42,6 +43,12 @@ class UserProfileUpdateView(UserProfileBaseMixin, UpdateView): | ||
| 42 | 43 | ||
| 43 | return obj | 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 | class UserProfileDetailView(UserProfileBaseMixin, DetailView): | 53 | class UserProfileDetailView(UserProfileBaseMixin, DetailView): |
| 47 | template_name = 'accounts/user_detail.html' | 54 | template_name = 'accounts/user_detail.html' |
colab/settings.py
colab/urls.py
| @@ -3,7 +3,7 @@ from django.conf import settings | @@ -3,7 +3,7 @@ from django.conf import settings | ||
| 3 | from django.views.generic import TemplateView | 3 | from django.views.generic import TemplateView |
| 4 | from django.contrib import admin | 4 | from django.contrib import admin |
| 5 | from django.views.generic import RedirectView | 5 | from django.views.generic import RedirectView |
| 6 | - | 6 | +from accounts.views import UserProfileUpdateView |
| 7 | 7 | ||
| 8 | admin.autodiscover() | 8 | admin.autodiscover() |
| 9 | 9 |
colab/utils/conf.py
| @@ -141,6 +141,44 @@ def load_colab_apps(): | @@ -141,6 +141,44 @@ def load_colab_apps(): | ||
| 141 | return {'COLAB_APPS': COLAB_APPS} | 141 | return {'COLAB_APPS': COLAB_APPS} |
| 142 | 142 | ||
| 143 | 143 | ||
| 144 | +def load_widgets_settings(): | ||
| 145 | + settings_file = os.getenv('COLAB_WIDGETS_SETTINGS', | ||
| 146 | + '/etc/colab/widgets_settings.py') | ||
| 147 | + settings_module = settings_file.split('.')[-2].split('/')[-1] | ||
| 148 | + py_path = "/".join(settings_file.split('/')[:-1]) | ||
| 149 | + logger.info('Widgets Settings file: %s', settings_file) | ||
| 150 | + | ||
| 151 | + if not os.path.exists(py_path): | ||
| 152 | + return | ||
| 153 | + | ||
| 154 | + original_path = sys.path | ||
| 155 | + sys.path.append(py_path) | ||
| 156 | + | ||
| 157 | + if os.path.exists(settings_file): | ||
| 158 | + importlib.import_module(settings_module) | ||
| 159 | + | ||
| 160 | + # Read settings from widgets.d | ||
| 161 | + settings_dir = os.getenv('COLAB_WIDGETS', '/etc/colab/widgets.d') | ||
| 162 | + logger.info('Widgets Settings directory: %s', settings_dir) | ||
| 163 | + sys.path = original_path | ||
| 164 | + | ||
| 165 | + if not os.path.exists(settings_dir): | ||
| 166 | + return | ||
| 167 | + | ||
| 168 | + for file_name in os.listdir(settings_dir): | ||
| 169 | + if not file_name.endswith('.py'): | ||
| 170 | + continue | ||
| 171 | + | ||
| 172 | + original_path = sys.path | ||
| 173 | + sys.path.append(settings_dir) | ||
| 174 | + | ||
| 175 | + file_module = file_name.split('.')[0] | ||
| 176 | + importlib.import_module(file_module) | ||
| 177 | + logger.info('Loaded %s/%s', settings_dir, file_name) | ||
| 178 | + | ||
| 179 | + sys.path = original_path | ||
| 180 | + | ||
| 181 | + | ||
| 144 | def validate_database(database_dict, default_db, debug): | 182 | def validate_database(database_dict, default_db, debug): |
| 145 | db_name = database_dict.get('default', {}).get('NAME') | 183 | db_name = database_dict.get('default', {}).get('NAME') |
| 146 | if not debug and db_name == default_db: | 184 | if not debug and db_name == default_db: |
| @@ -0,0 +1,54 @@ | @@ -0,0 +1,54 @@ | ||
| 1 | +from django.test import TestCase | ||
| 2 | + | ||
| 3 | +from colab.widgets.widget_manager import WidgetManager, Widget | ||
| 4 | + | ||
| 5 | + | ||
| 6 | +class WidgetManagerTest(TestCase): | ||
| 7 | + | ||
| 8 | + html_content = "<head><meta charset='UTF-8'></head><body><p>T</p></body>" | ||
| 9 | + widget_area = 'profile' | ||
| 10 | + widget_id = 'widget_id' | ||
| 11 | + | ||
| 12 | + def custom_widget_instance(self, content): | ||
| 13 | + | ||
| 14 | + class CustomWidget(Widget): | ||
| 15 | + identifier = 'widget_id' | ||
| 16 | + | ||
| 17 | + def generate_content(self, request=None): | ||
| 18 | + self.content = content | ||
| 19 | + return CustomWidget() | ||
| 20 | + | ||
| 21 | + def setUp(self): | ||
| 22 | + custom_widget = self.custom_widget_instance(self.html_content) | ||
| 23 | + WidgetManager.register_widget(self.widget_area, custom_widget) | ||
| 24 | + | ||
| 25 | + def tearDown(self): | ||
| 26 | + WidgetManager.unregister_widget(self.widget_area, self.widget_id) | ||
| 27 | + | ||
| 28 | + def test_add_widgets_to_key_area(self): | ||
| 29 | + self.assertEqual(len(WidgetManager.get_widgets(self.widget_area)), 1) | ||
| 30 | + | ||
| 31 | + def test_remove_widgets_in_key_area(self): | ||
| 32 | + area = 'admin' | ||
| 33 | + widget_instance = self.custom_widget_instance(self.html_content) | ||
| 34 | + | ||
| 35 | + WidgetManager.register_widget(area, widget_instance) | ||
| 36 | + WidgetManager.unregister_widget(area, self.widget_id) | ||
| 37 | + | ||
| 38 | + self.assertEqual(len(WidgetManager.get_widgets(area)), 0) | ||
| 39 | + | ||
| 40 | + def test_get_body(self): | ||
| 41 | + customWidget = self.custom_widget_instance(self.html_content) | ||
| 42 | + | ||
| 43 | + customWidget.generate_content() | ||
| 44 | + self.assertEqual(customWidget.get_body(), "<p>T</p>") | ||
| 45 | + | ||
| 46 | + def test_get_header(self): | ||
| 47 | + customWidget = self.custom_widget_instance(self.html_content) | ||
| 48 | + | ||
| 49 | + customWidget.generate_content() | ||
| 50 | + self.assertEqual(customWidget.get_header(), "<meta charset='UTF-8'>") | ||
| 51 | + | ||
| 52 | + def test_generate_content(self): | ||
| 53 | + widgets = WidgetManager.get_widgets(self.widget_area) | ||
| 54 | + self.assertEqual(widgets[0].content, self.html_content) |
| @@ -0,0 +1,60 @@ | @@ -0,0 +1,60 @@ | ||
| 1 | +from django.utils.safestring import mark_safe | ||
| 2 | + | ||
| 3 | + | ||
| 4 | +class Widget(object): | ||
| 5 | + identifier = None | ||
| 6 | + name = 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 category not in WidgetManager.widget_categories: | ||
| 41 | + WidgetManager.widget_categories[category] = [] | ||
| 42 | + | ||
| 43 | + WidgetManager.widget_categories[category].append(widget) | ||
| 44 | + | ||
| 45 | + @staticmethod | ||
| 46 | + def unregister_widget(category, widget_identifier): | ||
| 47 | + if category in WidgetManager.widget_categories: | ||
| 48 | + for widget in WidgetManager.widget_categories[category]: | ||
| 49 | + if widget.identifier == widget_identifier: | ||
| 50 | + WidgetManager.widget_categories[category].remove(widget) | ||
| 51 | + | ||
| 52 | + @staticmethod | ||
| 53 | + def get_widgets(category, request=None): | ||
| 54 | + if category not in WidgetManager.widget_categories: | ||
| 55 | + return [] | ||
| 56 | + | ||
| 57 | + widgets = WidgetManager.widget_categories[category] | ||
| 58 | + for widget in widgets: | ||
| 59 | + widget.generate_content(request) | ||
| 60 | + return widgets |
No preview for this file type
docs/source/dev.rst
| @@ -4,3 +4,47 @@ Developer Documentation | @@ -4,3 +4,47 @@ Developer Documentation | ||
| 4 | Getting Started | 4 | Getting Started |
| 5 | --------------- | 5 | --------------- |
| 6 | .. TODO | 6 | .. TODO |
| 7 | + | ||
| 8 | +Widgets | ||
| 9 | +------- | ||
| 10 | + | ||
| 11 | +A widget is a piece of HTML that will be inserted in a specific spot in a page to render some view. | ||
| 12 | + | ||
| 13 | +To create a new widget you need to extend the ``Widget`` class from ``colab.widgets``. In the child class you can override the methods below, but it is not mandatory: | ||
| 14 | + | ||
| 15 | +.. attribute:: get_header | ||
| 16 | + | ||
| 17 | + This method should return the HTML code to be used in the page's head. So, it will extract the head content from the ``content``. | ||
| 18 | + | ||
| 19 | +.. attribute:: get_body | ||
| 20 | + | ||
| 21 | + This method should return the HTML code to be used in the page's body. So, it will extract the body content from the ``content``. | ||
| 22 | + | ||
| 23 | +.. attribute:: generate_content | ||
| 24 | + | ||
| 25 | + This method will set the ``content`` when the widget is requested by the ``WidgetManager``. The ``content`` contains a HTML code that will be rendered in the target page. | ||
| 26 | + | ||
| 27 | +The Widget class has the following attributes: | ||
| 28 | + | ||
| 29 | +.. attribute:: identifier | ||
| 30 | + | ||
| 31 | + The identifier has to be a unique string across the widgets. | ||
| 32 | + | ||
| 33 | +.. attribute:: name | ||
| 34 | + | ||
| 35 | + The widget name is the string that will be used to render in the view, if needed. | ||
| 36 | + | ||
| 37 | +Example Widget: | ||
| 38 | + | ||
| 39 | +.. code-block:: python | ||
| 40 | + | ||
| 41 | + from colab.widgets.widget_manager import Widget | ||
| 42 | + | ||
| 43 | + class MyWidget(Widget): | ||
| 44 | + identifier = 'my_widget_id' | ||
| 45 | + name = 'My Widget' | ||
| 46 | + def generate_content(self, request): | ||
| 47 | + # process HTML content | ||
| 48 | + self.content = processed_content | ||
| 49 | + | ||
| 50 | +To add the widget in a view check the Widgets section in User Documentation. |
docs/source/user.rst
| @@ -58,6 +58,27 @@ View the following file: | @@ -58,6 +58,27 @@ View the following file: | ||
| 58 | 58 | ||
| 59 | The file /etc/colab/settings.py have the configurations of colab, this configurations overrides the django settings.py | 59 | The file /etc/colab/settings.py have the configurations of colab, this configurations overrides the django settings.py |
| 60 | 60 | ||
| 61 | +Widgets | ||
| 62 | +------- | ||
| 63 | + | ||
| 64 | +A widget is a piece of HTML that will be inserted in a specific spot in a page to render some view. | ||
| 65 | + | ||
| 66 | +To configure the widgets you have to edit, or create, the file ``/etc/colab/widgets_settings.py``. Or you can create a py file inside the folder ``/etc/colab/widgets.d``. | ||
| 67 | + | ||
| 68 | +Example: | ||
| 69 | + | ||
| 70 | +.. code-block:: python | ||
| 71 | + | ||
| 72 | + # Widget Manager handles all widgets and must be imported to register them | ||
| 73 | + from colab.widgets.widget_manager import WidgetManager | ||
| 74 | + | ||
| 75 | + # Specific code for Gitlab's Widget | ||
| 76 | + from colab_gitlab.widgets import GitlabProfileWidget | ||
| 77 | + | ||
| 78 | + WidgetManager.register_widget('profile', GitlabProfileWidget()) | ||
| 79 | + | ||
| 80 | + | ||
| 81 | +In this example the Gitlab's widget is added in a new tab inside the user profile. | ||
| 61 | 82 | ||
| 62 | Add a new plugin | 83 | Add a new plugin |
| 63 | ---------------- | 84 | ---------------- |
tests/run.py
| @@ -5,7 +5,9 @@ import sys | @@ -5,7 +5,9 @@ import sys | ||
| 5 | 5 | ||
| 6 | os.environ['DJANGO_SETTINGS_MODULE'] = 'colab.settings' | 6 | os.environ['DJANGO_SETTINGS_MODULE'] = 'colab.settings' |
| 7 | os.environ['COLAB_SETTINGS'] = 'tests/colab_settings.py' | 7 | os.environ['COLAB_SETTINGS'] = 'tests/colab_settings.py' |
| 8 | +os.environ['COLAB_WIDGETS_SETTINGS'] = 'tests/widgets_settings.py' | ||
| 8 | os.environ['COLAB_PLUGINS'] = 'tests/plugins.d' | 9 | os.environ['COLAB_PLUGINS'] = 'tests/plugins.d' |
| 10 | +os.environ['COLAB_WIDGETS'] = 'tests/widgets.d' | ||
| 9 | os.environ['COVERAGE_PROCESS_START'] = '.coveragerc' | 11 | os.environ['COVERAGE_PROCESS_START'] = '.coveragerc' |
| 10 | 12 | ||
| 11 | 13 |