Commit c943495e42fd09477a5a9c14038f7c233fc6e384
1 parent
152fb646
Exists in
master
and in
3 other branches
Adding webpage form (Not working yet/Adjusts to make)
Showing
30 changed files
with
775 additions
and
3 deletions
Show diff stats
amadeus/settings.py
| ... | ... | @@ -0,0 +1,170 @@ |
| 1 | +/** | |
| 2 | + * jQuery Formset 1.2 | |
| 3 | + * @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com) | |
| 4 | + * @requires jQuery 1.2.6 or later | |
| 5 | + * | |
| 6 | + * Copyright (c) 2009, Stanislaus Madueke | |
| 7 | + * All rights reserved. | |
| 8 | + * | |
| 9 | + * Licensed under the New BSD License | |
| 10 | + * See: http://www.opensource.org/licenses/bsd-license.php | |
| 11 | + */ | |
| 12 | +;(function($) { | |
| 13 | + $.fn.formset = function(opts) | |
| 14 | + { | |
| 15 | + var options = $.extend({}, $.fn.formset.defaults, opts), | |
| 16 | + flatExtraClasses = options.extraClasses.join(' '), | |
| 17 | + $$ = $(this), | |
| 18 | + | |
| 19 | + applyExtraClasses = function(row, ndx) { | |
| 20 | + if (options.extraClasses) { | |
| 21 | + row.removeClass(flatExtraClasses); | |
| 22 | + row.addClass(options.extraClasses[ndx % options.extraClasses.length]); | |
| 23 | + } | |
| 24 | + }, | |
| 25 | + | |
| 26 | + updateElementIndex = function(elem, prefix, ndx) { | |
| 27 | + var idRegex = new RegExp('(' + prefix + '-\\d+-)|(^)'), | |
| 28 | + replacement = prefix + '-' + ndx + '-'; | |
| 29 | + if (elem.attr("for")) elem.attr("for", elem.attr("for").replace(idRegex, replacement)); | |
| 30 | + if (elem.attr('id')) elem.attr('id', elem.attr('id').replace(idRegex, replacement)); | |
| 31 | + if (elem.attr('name')) elem.attr('name', elem.attr('name').replace(idRegex, replacement)); | |
| 32 | + }, | |
| 33 | + | |
| 34 | + hasChildElements = function(row) { | |
| 35 | + return row.find('input,select,textarea,label').length > 0; | |
| 36 | + }, | |
| 37 | + | |
| 38 | + insertDeleteLink = function(row) { | |
| 39 | + if (row.is('TR')) { | |
| 40 | + // If the forms are laid out in table rows, insert | |
| 41 | + // the remove button into the last table cell: | |
| 42 | + row.children(':last').append('<a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + '</a>'); | |
| 43 | + } else if (row.is('UL') || row.is('OL')) { | |
| 44 | + // If they're laid out as an ordered/unordered list, | |
| 45 | + // insert an <li> after the last list item: | |
| 46 | + row.append('<li><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a></li>'); | |
| 47 | + } else { | |
| 48 | + // Otherwise, just insert the remove button as the | |
| 49 | + // last child element of the form's container: | |
| 50 | + row.append('<a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a>'); | |
| 51 | + } | |
| 52 | + row.find('a.' + options.deleteCssClass).click(function() { | |
| 53 | + var row = $(this).parents('.' + options.formCssClass), | |
| 54 | + del = row.find('input:hidden[id $= "-DELETE"]'); | |
| 55 | + if (del.length) { | |
| 56 | + // We're dealing with an inline formset; rather than remove | |
| 57 | + // this form from the DOM, we'll mark it as deleted and hide | |
| 58 | + // it, then let Django handle the deleting: | |
| 59 | + del.val('on'); | |
| 60 | + row.hide(); | |
| 61 | + } else { | |
| 62 | + row.remove(); | |
| 63 | + // Update the TOTAL_FORMS form count. | |
| 64 | + // Also update names and IDs for all remaining form controls so they remain in sequence: | |
| 65 | + var forms = $('.' + options.formCssClass).not('.formset-custom-template'); | |
| 66 | + $('#id_' + options.prefix + '-TOTAL_FORMS').val(forms.length); | |
| 67 | + for (var i=0, formCount=forms.length; i<formCount; i++) { | |
| 68 | + applyExtraClasses(forms.eq(i), i); | |
| 69 | + forms.eq(i).find('input,select,textarea,label').each(function() { | |
| 70 | + updateElementIndex($(this), options.prefix, i); | |
| 71 | + }); | |
| 72 | + } | |
| 73 | + } | |
| 74 | + // If a post-delete callback was provided, call it with the deleted form: | |
| 75 | + if (options.removed) options.removed(row); | |
| 76 | + return false; | |
| 77 | + }); | |
| 78 | + }; | |
| 79 | + | |
| 80 | + $$.each(function(i) { | |
| 81 | + var row = $(this), | |
| 82 | + del = row.find('input:checkbox[id $= "-DELETE"]'); | |
| 83 | + if (del.length) { | |
| 84 | + // If you specify "can_delete = True" when creating an inline formset, | |
| 85 | + // Django adds a checkbox to each form in the formset. | |
| 86 | + // Replace the default checkbox with a hidden field: | |
| 87 | + del.before('<input type="hidden" name="' + del.attr('name') +'" id="' + del.attr('id') +'" />'); | |
| 88 | + del.remove(); | |
| 89 | + } | |
| 90 | + if (hasChildElements(row)) { | |
| 91 | + insertDeleteLink(row); | |
| 92 | + row.addClass(options.formCssClass); | |
| 93 | + applyExtraClasses(row, i); | |
| 94 | + } | |
| 95 | + }); | |
| 96 | + | |
| 97 | + if ($$.length) { | |
| 98 | + var addButton, template; | |
| 99 | + if (options.formTemplate) { | |
| 100 | + // If a form template was specified, we'll clone it to generate new form instances: | |
| 101 | + template = (options.formTemplate instanceof $) ? options.formTemplate : $(options.formTemplate); | |
| 102 | + template.removeAttr('id').addClass(options.formCssClass).addClass('formset-custom-template'); | |
| 103 | + template.find('input,select,textarea,label').each(function() { | |
| 104 | + updateElementIndex($(this), options.prefix, 2012); | |
| 105 | + }); | |
| 106 | + insertDeleteLink(template); | |
| 107 | + } else { | |
| 108 | + // Otherwise, use the last form in the formset; this works much better if you've got | |
| 109 | + // extra (>= 1) forms (thnaks to justhamade for pointing this out): | |
| 110 | + template = $('.' + options.formCssClass + ':last').clone(true).removeAttr('id'); | |
| 111 | + template.find('input:hidden[id $= "-DELETE"]').remove(); | |
| 112 | + template.find('input,select,textarea,label').each(function() { | |
| 113 | + var elem = $(this); | |
| 114 | + // If this is a checkbox or radiobutton, uncheck it. | |
| 115 | + // This fixes Issue 1, reported by Wilson.Andrew.J: | |
| 116 | + if (elem.is('input:checkbox') || elem.is('input:radio')) { | |
| 117 | + elem.attr('checked', false); | |
| 118 | + } else { | |
| 119 | + elem.val(''); | |
| 120 | + } | |
| 121 | + }); | |
| 122 | + } | |
| 123 | + // FIXME: Perhaps using $.data would be a better idea? | |
| 124 | + options.formTemplate = template; | |
| 125 | + | |
| 126 | + if ($$.attr('tagName') == 'TR') { | |
| 127 | + // If forms are laid out as table rows, insert the | |
| 128 | + // "add" button in a new table row: | |
| 129 | + var numCols = $$.eq(0).children().length; | |
| 130 | + $$.parent().append('<tr><td colspan="' + numCols + '"><a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a></tr>'); | |
| 131 | + addButton = $$.parent().find('tr:last a'); | |
| 132 | + addButton.parents('tr').addClass(options.formCssClass + '-add'); | |
| 133 | + } else { | |
| 134 | + // Otherwise, insert it immediately after the last form: | |
| 135 | + $$.filter(':last').after('<a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a>'); | |
| 136 | + addButton = $$.filter(':last').next(); | |
| 137 | + } | |
| 138 | + addButton.click(function() { | |
| 139 | + var formCount = parseInt($('#id_' + options.prefix + '-TOTAL_FORMS').val()), | |
| 140 | + row = options.formTemplate.clone(true).removeClass('formset-custom-template'), | |
| 141 | + buttonRow = $(this).parents('tr.' + options.formCssClass + '-add').get(0) || this; | |
| 142 | + applyExtraClasses(row, formCount); | |
| 143 | + row.insertBefore($(buttonRow)).show(); | |
| 144 | + row.find('input,select,textarea,label').each(function() { | |
| 145 | + updateElementIndex($(this), options.prefix, formCount); | |
| 146 | + }); | |
| 147 | + $('#id_' + options.prefix + '-TOTAL_FORMS').val(formCount + 1); | |
| 148 | + // If a post-add callback was supplied, call it with the added form: | |
| 149 | + if (options.added) options.added(row); | |
| 150 | + return false; | |
| 151 | + }); | |
| 152 | + } | |
| 153 | + | |
| 154 | + return $$; | |
| 155 | + } | |
| 156 | + | |
| 157 | + /* Setup plugin defaults */ | |
| 158 | + $.fn.formset.defaults = { | |
| 159 | + prefix: 'form', // The form prefix for your django formset | |
| 160 | + formTemplate: null, // The jQuery selection cloned to generate new form instances | |
| 161 | + addText: 'add another', // Text for the add link | |
| 162 | + deleteText: 'remove', // Text for the delete link | |
| 163 | + addCssClass: 'add-row', // CSS class applied to the add link | |
| 164 | + deleteCssClass: 'delete-row', // CSS class applied to the delete link | |
| 165 | + formCssClass: 'dynamic-form', // CSS class applied to each form in a formset | |
| 166 | + extraClasses: [], // Additional CSS classes, which will be applied to each form in turn | |
| 167 | + added: null, // Function called each time a new form is added | |
| 168 | + removed: null // Function called each time a form is deleted | |
| 169 | + }; | |
| 170 | +})(jQuery) | ... | ... |
amadeus/static/js/topics.js
| ... | ... | @@ -73,4 +73,12 @@ function delete_topic(url) { |
| 73 | 73 | |
| 74 | 74 | $('.modal').modal('show'); |
| 75 | 75 | }); |
| 76 | -} | |
| 77 | 76 | \ No newline at end of file |
| 77 | +} | |
| 78 | + | |
| 79 | +$(".add_resource").on('show.bs.dropdown', function () { | |
| 80 | + $(this).find('i').switchClass("fa-angle-right", "fa-angle-down", 250, "easeInOutQuad"); | |
| 81 | +}); | |
| 82 | + | |
| 83 | +$(".add_resource").on('hide.bs.dropdown', function () { | |
| 84 | + $(this).find('i').switchClass("fa-angle-down", "fa-angle-right", 250, "easeInOutQuad"); | |
| 85 | +}); | |
| 78 | 86 | \ No newline at end of file | ... | ... |
amadeus/urls.py
| ... | ... | @@ -29,6 +29,7 @@ urlpatterns = [ |
| 29 | 29 | url(r'^subjects/', include('subjects.urls', namespace = 'subjects')), |
| 30 | 30 | url(r'^groups/', include('students_group.urls', namespace = 'groups')), |
| 31 | 31 | url(r'^topics/', include('topics.urls', namespace = 'topics')), |
| 32 | + url(r'^webpages/', include('webpage.urls', namespace = 'webpages')), | |
| 32 | 33 | url(r'^mailsender/', include('mailsender.urls', namespace = 'mailsender')), |
| 33 | 34 | url(r'^security/', include('security.urls', namespace = 'security')), |
| 34 | 35 | url(r'^themes/', include('themes.urls', namespace = 'themes')), | ... | ... |
| ... | ... | @@ -0,0 +1,10 @@ |
| 1 | +# coding=utf-8 | |
| 2 | +from django import forms | |
| 3 | +from django.utils.translation import ugettext_lazy as _ | |
| 4 | + | |
| 5 | +from .models import Pendencies | |
| 6 | + | |
| 7 | +class PendenciesForm(forms.ModelForm): | |
| 8 | + class Meta: | |
| 9 | + model = Pendencies | |
| 10 | + fields = ['action', 'begin_date', 'end_date'] | |
| 0 | 11 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -0,0 +1,25 @@ |
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | +# Generated by Django 1.10 on 2017-01-19 21:09 | |
| 3 | +from __future__ import unicode_literals | |
| 4 | + | |
| 5 | +from django.db import migrations, models | |
| 6 | + | |
| 7 | + | |
| 8 | +class Migration(migrations.Migration): | |
| 9 | + | |
| 10 | + initial = True | |
| 11 | + | |
| 12 | + dependencies = [ | |
| 13 | + ] | |
| 14 | + | |
| 15 | + operations = [ | |
| 16 | + migrations.CreateModel( | |
| 17 | + name='Pendencies', | |
| 18 | + fields=[ | |
| 19 | + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
| 20 | + ('action', models.CharField(choices=[('view', 'Visualize'), ('create', 'Create'), ('answer', 'Answer'), ('access', 'Access')], max_length=100, verbose_name='Action')), | |
| 21 | + ('begin_date', models.DateTimeField(blank=True, null=True, verbose_name='Begin Date')), | |
| 22 | + ('end_date', models.DateTimeField(blank=True, null=True, verbose_name='End Date')), | |
| 23 | + ], | |
| 24 | + ), | |
| 25 | + ] | ... | ... |
| ... | ... | @@ -0,0 +1,24 @@ |
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | +# Generated by Django 1.10 on 2017-01-19 21:09 | |
| 3 | +from __future__ import unicode_literals | |
| 4 | + | |
| 5 | +from django.db import migrations, models | |
| 6 | +import django.db.models.deletion | |
| 7 | + | |
| 8 | + | |
| 9 | +class Migration(migrations.Migration): | |
| 10 | + | |
| 11 | + initial = True | |
| 12 | + | |
| 13 | + dependencies = [ | |
| 14 | + ('topics', '0005_resource'), | |
| 15 | + ('pendencies', '0001_initial'), | |
| 16 | + ] | |
| 17 | + | |
| 18 | + operations = [ | |
| 19 | + migrations.AddField( | |
| 20 | + model_name='pendencies', | |
| 21 | + name='resource', | |
| 22 | + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='pendencies_resource', to='topics.Resource', verbose_name='Resource'), | |
| 23 | + ), | |
| 24 | + ] | ... | ... |
| ... | ... | @@ -0,0 +1,11 @@ |
| 1 | +from django.db import models | |
| 2 | +from autoslug.fields import AutoSlugField | |
| 3 | +from django.utils.translation import ugettext_lazy as _ | |
| 4 | + | |
| 5 | +from topics.models import Resource | |
| 6 | + | |
| 7 | +class Pendencies(models.Model): | |
| 8 | + action = models.CharField(_('Action'), max_length = 100, choices = (("view", _("Visualize")), ("create", _("Create")), ("answer", _("Answer")), ("access", _("Access")))) | |
| 9 | + begin_date = models.DateTimeField(_('Begin Date'), null = True, blank = True) | |
| 10 | + end_date = models.DateTimeField(_('End Date'), null = True, blank = True) | |
| 11 | + resource = models.ForeignKey(Resource, verbose_name = _('Resource'), related_name = 'pendencies_resource', null = True) | |
| 0 | 12 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -0,0 +1,19 @@ |
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | +# Generated by Django 1.10 on 2017-01-19 18:43 | |
| 3 | +from __future__ import unicode_literals | |
| 4 | + | |
| 5 | +from django.db import migrations | |
| 6 | + | |
| 7 | + | |
| 8 | +class Migration(migrations.Migration): | |
| 9 | + | |
| 10 | + dependencies = [ | |
| 11 | + ('students_group', '0002_auto_20170118_1800'), | |
| 12 | + ] | |
| 13 | + | |
| 14 | + operations = [ | |
| 15 | + migrations.AlterModelOptions( | |
| 16 | + name='studentsgroup', | |
| 17 | + options={'ordering': ['name'], 'verbose_name': 'Students Group', 'verbose_name_plural': 'Students Groups'}, | |
| 18 | + ), | |
| 19 | + ] | ... | ... |
| ... | ... | @@ -0,0 +1,44 @@ |
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | +# Generated by Django 1.10 on 2017-01-19 21:09 | |
| 3 | +from __future__ import unicode_literals | |
| 4 | + | |
| 5 | +import autoslug.fields | |
| 6 | +from django.conf import settings | |
| 7 | +from django.db import migrations, models | |
| 8 | +import django.db.models.deletion | |
| 9 | + | |
| 10 | + | |
| 11 | +class Migration(migrations.Migration): | |
| 12 | + | |
| 13 | + dependencies = [ | |
| 14 | + ('subjects', '0012_auto_20170112_1408'), | |
| 15 | + migrations.swappable_dependency(settings.AUTH_USER_MODEL), | |
| 16 | + ('students_group', '0003_auto_20170119_1543'), | |
| 17 | + ('topics', '0004_auto_20170118_1711'), | |
| 18 | + ] | |
| 19 | + | |
| 20 | + operations = [ | |
| 21 | + migrations.CreateModel( | |
| 22 | + name='Resource', | |
| 23 | + fields=[ | |
| 24 | + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
| 25 | + ('name', models.CharField(max_length=200, verbose_name='Name')), | |
| 26 | + ('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', unique=True, verbose_name='Slug')), | |
| 27 | + ('brief_description', models.TextField(blank=True, verbose_name='Brief Description')), | |
| 28 | + ('show_window', models.BooleanField(default=False, verbose_name='Show in new window')), | |
| 29 | + ('all_students', models.BooleanField(default=False, verbose_name='All Students')), | |
| 30 | + ('visible', models.BooleanField(default=True, verbose_name='Visible')), | |
| 31 | + ('order', models.PositiveSmallIntegerField(null=True, verbose_name='Order')), | |
| 32 | + ('create_date', models.DateTimeField(auto_now_add=True, verbose_name='Create Date')), | |
| 33 | + ('last_update', models.DateTimeField(auto_now=True, verbose_name='Last Update')), | |
| 34 | + ('groups', models.ManyToManyField(blank=True, related_name='resource_groups', to='students_group.StudentsGroup', verbose_name='Groups')), | |
| 35 | + ('students', models.ManyToManyField(blank=True, related_name='resource_students', to=settings.AUTH_USER_MODEL, verbose_name='Students')), | |
| 36 | + ('tags', models.ManyToManyField(blank=True, related_name='resource_tags', to='subjects.Tag', verbose_name='Markers')), | |
| 37 | + ('topic', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='resource_topic', to='topics.Topic', verbose_name='Topic')), | |
| 38 | + ], | |
| 39 | + options={ | |
| 40 | + 'verbose_name': 'Resource', | |
| 41 | + 'verbose_name_plural': 'Resources', | |
| 42 | + }, | |
| 43 | + ), | |
| 44 | + ] | ... | ... |
topics/models.py
| ... | ... | @@ -2,7 +2,9 @@ from django.db import models |
| 2 | 2 | from autoslug.fields import AutoSlugField |
| 3 | 3 | from django.utils.translation import ugettext_lazy as _ |
| 4 | 4 | |
| 5 | -from subjects.models import Subject | |
| 5 | +from subjects.models import Subject, Tag | |
| 6 | +from students_group.models import StudentsGroup | |
| 7 | +from users.models import User | |
| 6 | 8 | |
| 7 | 9 | class Topic(models.Model): |
| 8 | 10 | name = models.CharField(_('Name'), max_length = 200) |
| ... | ... | @@ -21,4 +23,26 @@ class Topic(models.Model): |
| 21 | 23 | ordering = ['order'] |
| 22 | 24 | |
| 23 | 25 | def __str__(self): |
| 24 | - return self.name | |
| 25 | 26 | \ No newline at end of file |
| 27 | + return self.name | |
| 28 | + | |
| 29 | +class Resource(models.Model): | |
| 30 | + name = models.CharField(_('Name'), max_length = 200) | |
| 31 | + slug = AutoSlugField(_("Slug"), populate_from = 'name', unique = True) | |
| 32 | + brief_description = models.TextField(_('Brief Description'), blank = True) | |
| 33 | + show_window = models.BooleanField(_('Show in new window'), default = False) | |
| 34 | + all_students = models.BooleanField(_('All Students'), default = False) | |
| 35 | + visible = models.BooleanField(_('Visible'), default = True) | |
| 36 | + order = models.PositiveSmallIntegerField(_('Order'), null = True) | |
| 37 | + topic = models.ForeignKey(Topic, verbose_name = _('Topic'), related_name = "resource_topic", null = True) | |
| 38 | + students = models.ManyToManyField(User, verbose_name = _('Students'), related_name = 'resource_students', blank = True) | |
| 39 | + groups = models.ManyToManyField(StudentsGroup, verbose_name = _('Groups'), related_name = 'resource_groups', blank = True) | |
| 40 | + tags = models.ManyToManyField(Tag, verbose_name = _('Markers'), related_name = 'resource_tags', blank = True) | |
| 41 | + create_date = models.DateTimeField(_('Create Date'), auto_now_add = True) | |
| 42 | + last_update = models.DateTimeField(_('Last Update'), auto_now = True) | |
| 43 | + | |
| 44 | + class Meta: | |
| 45 | + verbose_name = _('Resource') | |
| 46 | + verbose_name_plural = _('Resources') | |
| 47 | + | |
| 48 | + def __str__(self): | |
| 49 | + return self.name | ... | ... |
topics/templates/topics/list.html
| ... | ... | @@ -41,6 +41,23 @@ |
| 41 | 41 | {% autoescape off %} |
| 42 | 42 | {{ topic.description }} |
| 43 | 43 | {% endautoescape %} |
| 44 | + | |
| 45 | + {% if has_subject_permissions %} | |
| 46 | + <div class="row text-center"> | |
| 47 | + <div class="btn-group add_resource"> | |
| 48 | + <button type="button" class="btn btn-success btn-raised dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> | |
| 49 | + <i class="fa fa-angle-right"></i> {% trans 'Add Resource' %} | |
| 50 | + </button> | |
| 51 | + <ul class="dropdown-menu"> | |
| 52 | + <li><a href="#">{% trans 'Video Embed' %}</a></li> | |
| 53 | + <li><a href="#">{% trans 'Forum' %}</a></li> | |
| 54 | + <li><a href="#">{% trans 'File Link' %}</a></li> | |
| 55 | + <li><a href="{% url 'webpages:create' topic.slug %}">{% trans 'Webpage' %}</a></li> | |
| 56 | + <li><a href="#">{% trans 'Questionary' %}</a></li> | |
| 57 | + </ul> | |
| 58 | + </div> | |
| 59 | + </div> | |
| 60 | + {% endif %} | |
| 44 | 61 | </div> |
| 45 | 62 | </div> |
| 46 | 63 | {% endif %} | ... | ... |
| ... | ... | @@ -0,0 +1,24 @@ |
| 1 | +# coding=utf-8 | |
| 2 | +from django import forms | |
| 3 | +from django.forms.models import inlineformset_factory | |
| 4 | +from django.utils.translation import ugettext_lazy as _ | |
| 5 | + | |
| 6 | +from pendencies.forms import PendenciesForm | |
| 7 | +from pendencies.models import Pendencies | |
| 8 | + | |
| 9 | +from .models import Webpage | |
| 10 | + | |
| 11 | +class WebpageForm(forms.ModelForm): | |
| 12 | + tags = forms.CharField(label = _('Tags'), required = False) | |
| 13 | + | |
| 14 | + class Meta: | |
| 15 | + model = Webpage | |
| 16 | + fields = ['name', 'content', 'brief_description', 'all_students', 'students', 'groups', 'show_window', 'visible'] | |
| 17 | + widgets = { | |
| 18 | + 'content': forms.Textarea, | |
| 19 | + 'brief_description': forms.Textarea, | |
| 20 | + 'students': forms.SelectMultiple, | |
| 21 | + 'groups': forms.SelectMultiple, | |
| 22 | + } | |
| 23 | + | |
| 24 | +InlinePendenciesFormset = inlineformset_factory(Webpage, Pendencies, form = PendenciesForm, extra = 1, can_delete = True) | |
| 0 | 25 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -0,0 +1,30 @@ |
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | +# Generated by Django 1.10 on 2017-01-19 21:09 | |
| 3 | +from __future__ import unicode_literals | |
| 4 | + | |
| 5 | +from django.db import migrations, models | |
| 6 | +import django.db.models.deletion | |
| 7 | + | |
| 8 | + | |
| 9 | +class Migration(migrations.Migration): | |
| 10 | + | |
| 11 | + initial = True | |
| 12 | + | |
| 13 | + dependencies = [ | |
| 14 | + ('topics', '0005_resource'), | |
| 15 | + ] | |
| 16 | + | |
| 17 | + operations = [ | |
| 18 | + migrations.CreateModel( | |
| 19 | + name='Webpage', | |
| 20 | + fields=[ | |
| 21 | + ('resource_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='topics.Resource')), | |
| 22 | + ('content', models.TextField(verbose_name='HTML Page Content')), | |
| 23 | + ], | |
| 24 | + options={ | |
| 25 | + 'verbose_name': 'WebPage', | |
| 26 | + 'verbose_name_plural': 'WebPages', | |
| 27 | + }, | |
| 28 | + bases=('topics.resource',), | |
| 29 | + ), | |
| 30 | + ] | ... | ... |
| ... | ... | @@ -0,0 +1,15 @@ |
| 1 | +from django.db import models | |
| 2 | +from autoslug.fields import AutoSlugField | |
| 3 | +from django.utils.translation import ugettext_lazy as _ | |
| 4 | + | |
| 5 | +from topics.models import Resource | |
| 6 | + | |
| 7 | +class Webpage(Resource): | |
| 8 | + content = models.TextField(_('HTML Page Content')) | |
| 9 | + | |
| 10 | + class Meta: | |
| 11 | + verbose_name = _('WebPage') | |
| 12 | + verbose_name_plural = _('WebPages') | |
| 13 | + | |
| 14 | + def __str__(self): | |
| 15 | + return self.name | |
| 0 | 16 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -0,0 +1,224 @@ |
| 1 | +{% load static i18n %} | |
| 2 | +{% load widget_tweaks %} | |
| 3 | + | |
| 4 | +<form method="post" action="" enctype="multipart/form-data"> | |
| 5 | + {% csrf_token %} | |
| 6 | + | |
| 7 | + <div class="form-group{% if form.has_error %} has-error {% endif %} is-fileinput"> | |
| 8 | + <label for="{{ form.name.auto_id }}">{{ form.name.label }} <span>*</span></label> | |
| 9 | + {% render_field form.name class='form-control' %} | |
| 10 | + </div> | |
| 11 | + | |
| 12 | + <div class="form-group{% if form.has_error %} has-error {% endif %} is-fileinput"> | |
| 13 | + <label for="{{ form.content.auto_id }}">{{ form.content.label }} <span>*</span></label> | |
| 14 | + {% render_field form.content class='form-control text_wysiwyg' %} | |
| 15 | + </div> | |
| 16 | + | |
| 17 | + <legend>{% trans 'Common resources settings' %}</legend> | |
| 18 | + | |
| 19 | + <div class="form-group{% if form.has_error %} has-error {% endif %} is-fileinput"> | |
| 20 | + <label for="{{ form.brief_description.auto_id }}">{{ form.brief_description.label }}</label> | |
| 21 | + {% render_field form.brief_description class='form-control text_wysiwyg' %} | |
| 22 | + </div> | |
| 23 | + | |
| 24 | + <div class="form-group{% if form.has_error %} has-error {% endif %} is-fileinput"> | |
| 25 | + <label for="{{ form.tags.auto_id }}">{{ form.tags.label }}</label> | |
| 26 | + {% render_field form.tags class='form-control' data-role="tagsinput" %} | |
| 27 | + </div> | |
| 28 | + | |
| 29 | + <div class="panel-group" id="professors_accordion" role="tablist" aria-multiselectable="true"> | |
| 30 | + <div class="panel panel-info"> | |
| 31 | + <div class="panel-heading"> | |
| 32 | + <div class="row"> | |
| 33 | + <div class="col-md-12"> | |
| 34 | + <a data-parent="#professors_accordion" data-toggle="collapse" href="#notifications"> | |
| 35 | + <h4 class="panel-title"> | |
| 36 | + <button class="btn btn-default btn-xs text-center cat-selector"><i class="fa fa-angle-right fa-2x" aria-hidden="true"></i></button><label>{% trans 'Pendencies Notifications' %}</label> | |
| 37 | + </h4> | |
| 38 | + </a> | |
| 39 | + </div> | |
| 40 | + </div> | |
| 41 | + </div> | |
| 42 | + <div id="notifications" class="panel-collapse collapse"> | |
| 43 | + {{ pendencies_form.management_form }} | |
| 44 | + {{ pendencies_form.non_form_errors }} | |
| 45 | + | |
| 46 | + {% for notify in pendencies_form %} | |
| 47 | + <div class="notifies"> | |
| 48 | + {% if notify.instance.pk %}{{ notify.DELETE }}{% endif %} | |
| 49 | + <div class="form-group{% if form.has_error %} has-error {% endif %}"> | |
| 50 | + <label for="{{ notify.action.auto_id }}"> | |
| 51 | + {{ notify.action.label }} {% render_field notify.action class='form-control' %} | |
| 52 | + </label> | |
| 53 | + </div> | |
| 54 | + <div class="form-group{% if form.has_error %} has-error {% endif %}"> | |
| 55 | + <div class=" checkbox"> | |
| 56 | + <label> | |
| 57 | + <input type="checkbox" class="begin_date" /> {{ notify.begin_date.label }} | |
| 58 | + </label> | |
| 59 | + {% render_field notify.begin_date class='form-control' %} | |
| 60 | + </div> | |
| 61 | + </div> | |
| 62 | + <div class="form-group{% if form.has_error %} has-error {% endif %}"> | |
| 63 | + <div class=" checkbox"> | |
| 64 | + <label> | |
| 65 | + <input type="checkbox" class="end_date" /> {{ notify.end_date.label }} | |
| 66 | + </label> | |
| 67 | + {% render_field notify.end_date class='form-control' %} | |
| 68 | + </div> | |
| 69 | + </div> | |
| 70 | + </div> | |
| 71 | + {% endfor %} | |
| 72 | + </div> | |
| 73 | + </div> | |
| 74 | + | |
| 75 | + <div class="panel panel-info"> | |
| 76 | + <div class="panel-heading"> | |
| 77 | + <div class="row"> | |
| 78 | + <div class="col-md-12"> | |
| 79 | + <a data-parent="#professors_accordion" data-toggle="collapse" href="#students"> | |
| 80 | + <h4 class="panel-title"> | |
| 81 | + <button class="btn btn-default btn-xs text-center cat-selector"><i class="fa fa-angle-right fa-2x" aria-hidden="true"></i></button><label for="{{ form.students.auto_id }}">{{ form.students.label }}</label> | |
| 82 | + </h4> | |
| 83 | + </a> | |
| 84 | + </div> | |
| 85 | + </div> | |
| 86 | + </div> | |
| 87 | + <div id="students" class="panel-collapse collapse"> | |
| 88 | + <div class="form-group{% if form.has_error %} has-error {% endif %}"> | |
| 89 | + <div class=" checkbox"> | |
| 90 | + <label for="{{ form.all_students.auto_id }}"> | |
| 91 | + {% render_field form.all_students %} {{ form.all_students.label }} | |
| 92 | + </label> | |
| 93 | + </div> | |
| 94 | + </div> | |
| 95 | + | |
| 96 | + <p><em>{% trans 'Attribute students to webpage' %}:</em></p> | |
| 97 | + {% render_field form.students class='form-control' %} | |
| 98 | + | |
| 99 | + <br clear="all" /> | |
| 100 | + | |
| 101 | + <p><em>{% trans 'Attribute groups to webpage' %}:</em></p> | |
| 102 | + {% render_field form.groups class='form-control' %} | |
| 103 | + </div> | |
| 104 | + </div> | |
| 105 | + </div> | |
| 106 | + | |
| 107 | + <div class="form-group{% if form.has_error %} has-error {% endif %}"> | |
| 108 | + <div class=" checkbox"> | |
| 109 | + <label for="{{ form.show_window.auto_id }}"> | |
| 110 | + {% render_field form.show_window %} {{ form.show_window.label }} | |
| 111 | + </label> | |
| 112 | + </div> | |
| 113 | + </div> | |
| 114 | + | |
| 115 | + <div class="form-group{% if form.has_error %} has-error {% endif %}"> | |
| 116 | + <div class=" checkbox"> | |
| 117 | + <label for="{{ form.visible.auto_id }}"> | |
| 118 | + {% render_field form.visible %} {{ form.visible.label }} | |
| 119 | + </label> | |
| 120 | + </div> | |
| 121 | + </div> | |
| 122 | + | |
| 123 | + <div class="col-md-12 col-lg-12 col-sm-12 col-xs-12"> | |
| 124 | + <div class="text-center"> | |
| 125 | + <input type="submit" value="{% trans 'Save' %}" class="btn btn-raised btn-success" /> | |
| 126 | + </div> | |
| 127 | + </div> | |
| 128 | +</form> | |
| 129 | +<script type="text/javascript"> | |
| 130 | + $(function() { | |
| 131 | + $('.notifies').formset({ | |
| 132 | + addText: '{% trans "Add new notification" %}', | |
| 133 | + deleteText: '{% trans "Remove this" %}' | |
| 134 | + }); | |
| 135 | + }); | |
| 136 | + | |
| 137 | + $('#id_groups').multiSelect({ | |
| 138 | + selectableHeader: "<input type='text' class='search-input category-search-users' autocomplete='off' placeholder=' '>", | |
| 139 | + selectionHeader: "<input type='text' class='search-input category-search-users' autocomplete='off' placeholder=''>", | |
| 140 | + afterInit: function(ms){ | |
| 141 | + var that = this, | |
| 142 | + $selectableSearch = that.$selectableUl.prev(), | |
| 143 | + $selectionSearch = that.$selectionUl.prev(), | |
| 144 | + selectableSearchString = '#'+that.$container.attr('id')+' .ms-elem-selectable:not(.ms-selected)', | |
| 145 | + selectionSearchString = '#'+that.$container.attr('id')+' .ms-elem-selection.ms-selected'; | |
| 146 | + | |
| 147 | + that.qs1 = $selectableSearch.quicksearch(selectableSearchString) | |
| 148 | + .on('keydown', function(e){ | |
| 149 | + if (e.which === 40){ | |
| 150 | + that.$selectableUl.focus(); | |
| 151 | + return false; | |
| 152 | + } | |
| 153 | + }); | |
| 154 | + | |
| 155 | + that.qs2 = $selectionSearch.quicksearch(selectionSearchString) | |
| 156 | + .on('keydown', function(e){ | |
| 157 | + if (e.which == 40){ | |
| 158 | + that.$selectionUl.focus(); | |
| 159 | + return false; | |
| 160 | + } | |
| 161 | + }); | |
| 162 | + }, | |
| 163 | + afterSelect: function(){ | |
| 164 | + this.qs1.cache(); | |
| 165 | + this.qs2.cache(); | |
| 166 | + }, | |
| 167 | + afterDeselect: function(){ | |
| 168 | + this.qs1.cache(); | |
| 169 | + this.qs2.cache(); | |
| 170 | + } | |
| 171 | + });// Used to create multi-select css style | |
| 172 | + | |
| 173 | + $('#id_students').multiSelect({ | |
| 174 | + selectableHeader: "<input type='text' class='search-input category-search-users' autocomplete='off' placeholder=' '>", | |
| 175 | + selectionHeader: "<input type='text' class='search-input category-search-users' autocomplete='off' placeholder=''>", | |
| 176 | + afterInit: function(ms){ | |
| 177 | + var that = this, | |
| 178 | + $selectableSearch = that.$selectableUl.prev(), | |
| 179 | + $selectionSearch = that.$selectionUl.prev(), | |
| 180 | + selectableSearchString = '#'+that.$container.attr('id')+' .ms-elem-selectable:not(.ms-selected)', | |
| 181 | + selectionSearchString = '#'+that.$container.attr('id')+' .ms-elem-selection.ms-selected'; | |
| 182 | + | |
| 183 | + that.qs1 = $selectableSearch.quicksearch(selectableSearchString) | |
| 184 | + .on('keydown', function(e){ | |
| 185 | + if (e.which === 40){ | |
| 186 | + that.$selectableUl.focus(); | |
| 187 | + return false; | |
| 188 | + } | |
| 189 | + }); | |
| 190 | + | |
| 191 | + that.qs2 = $selectionSearch.quicksearch(selectionSearchString) | |
| 192 | + .on('keydown', function(e){ | |
| 193 | + if (e.which == 40){ | |
| 194 | + that.$selectionUl.focus(); | |
| 195 | + return false; | |
| 196 | + } | |
| 197 | + }); | |
| 198 | + }, | |
| 199 | + afterSelect: function(){ | |
| 200 | + this.qs1.cache(); | |
| 201 | + this.qs2.cache(); | |
| 202 | + }, | |
| 203 | + afterDeselect: function(){ | |
| 204 | + this.qs1.cache(); | |
| 205 | + this.qs2.cache(); | |
| 206 | + } | |
| 207 | + });// Used to create multi-select css style | |
| 208 | + | |
| 209 | + $('.collapse').on('show.bs.collapse', function (e) { | |
| 210 | + if($(this).is(e.target)){ | |
| 211 | + var btn = $(this).parent().find('.fa-angle-right'); | |
| 212 | + | |
| 213 | + btn.switchClass("fa-angle-right", "fa-angle-down", 250, "easeInOutQuad"); | |
| 214 | + } | |
| 215 | + }); | |
| 216 | + | |
| 217 | + $('.collapse').on('hide.bs.collapse', function (e) { | |
| 218 | + if($(this).is(e.target)){ | |
| 219 | + var btn = $(this).parent().find('.fa-angle-down'); | |
| 220 | + | |
| 221 | + btn.switchClass("fa-angle-down", "fa-angle-right", 250, "easeInOutQuad"); | |
| 222 | + } | |
| 223 | + }); | |
| 224 | +</script> | |
| 0 | 225 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -0,0 +1,35 @@ |
| 1 | +{% extends 'subjects/view.html' %} | |
| 2 | + | |
| 3 | +{% load static i18n django_bootstrap_breadcrumbs %} | |
| 4 | + | |
| 5 | +{% block style %} | |
| 6 | + {{block.super}} | |
| 7 | + <link rel="stylesheet" type="text/css" href="{% static "css/bootstrap-tagsinput.css" %}"> | |
| 8 | +{% endblock %} | |
| 9 | + | |
| 10 | +{% block javascript %} | |
| 11 | + {{block.super}} | |
| 12 | + <script type="text/javascript" src="{% static "js/bootstrap-tagsinput.js" %} "></script> | |
| 13 | + <script type="text/javascript" src="{% static "js/jquery.formset.js" %} "></script> | |
| 14 | +{% endblock %} | |
| 15 | + | |
| 16 | +{% block breadcrumbs %} | |
| 17 | + {{ block.super }} | |
| 18 | + | |
| 19 | + {% breadcrumb topic 'subjects:view' topic.subject.slug %} | |
| 20 | + | |
| 21 | + {% trans 'Create Webpage' as bread %} | |
| 22 | + {% breadcrumb bread 'webpages:create' topic.slug %} | |
| 23 | +{% endblock %} | |
| 24 | + | |
| 25 | +{% block content %} | |
| 26 | + <div class="card"> | |
| 27 | + <div class="card-content"> | |
| 28 | + <div class="card-body"> | |
| 29 | + {% include 'webpages/_form.html' %} | |
| 30 | + </div> | |
| 31 | + </div> | |
| 32 | + </div> | |
| 33 | + <br clear="all" /> | |
| 34 | + <br clear="all" /> | |
| 35 | +{% endblock %} | ... | ... |
| ... | ... | @@ -0,0 +1,56 @@ |
| 1 | +from django.shortcuts import get_object_or_404, redirect, render | |
| 2 | +from django.views import generic | |
| 3 | +from django.contrib import messages | |
| 4 | +from django.core.urlresolvers import reverse, reverse_lazy | |
| 5 | +from django.utils.translation import ugettext_lazy as _ | |
| 6 | +from django.contrib.auth.mixins import LoginRequiredMixin | |
| 7 | + | |
| 8 | +from amadeus.permissions import has_subject_permissions | |
| 9 | + | |
| 10 | +from topics.models import Topic | |
| 11 | + | |
| 12 | +from .forms import WebpageForm, InlinePendenciesFormset | |
| 13 | +from .models import Webpage | |
| 14 | + | |
| 15 | +class CreateView(LoginRequiredMixin, generic.edit.CreateView): | |
| 16 | + login_url = reverse_lazy("users:login") | |
| 17 | + redirect_field_name = 'next' | |
| 18 | + | |
| 19 | + template_name = 'webpages/create.html' | |
| 20 | + form_class = WebpageForm | |
| 21 | + | |
| 22 | + def dispatch(self, request, *args, **kwargs): | |
| 23 | + slug = self.kwargs.get('slug', '') | |
| 24 | + topic = get_object_or_404(Topic, slug = slug) | |
| 25 | + | |
| 26 | + if not has_subject_permissions(request.user, topic.subject): | |
| 27 | + return redirect(reverse_lazy('subjects:home')) | |
| 28 | + | |
| 29 | + return super(CreateView, self).dispatch(request, *args, **kwargs) | |
| 30 | + | |
| 31 | + def get(self, request, *args, **kwargs): | |
| 32 | + self.object = None | |
| 33 | + | |
| 34 | + form_class = self.get_form_class() | |
| 35 | + form = self.get_form(form_class) | |
| 36 | + pendencies_form = InlinePendenciesFormset() | |
| 37 | + | |
| 38 | + return self.render_to_response(self.get_context_data(form = form,pendencies_form = pendencies_form)) | |
| 39 | + | |
| 40 | + def get_context_data(self, **kwargs): | |
| 41 | + context = super(CreateView, self).get_context_data(**kwargs) | |
| 42 | + | |
| 43 | + context['title'] = _('Create Webpage') | |
| 44 | + | |
| 45 | + slug = self.kwargs.get('slug', '') | |
| 46 | + topic = get_object_or_404(Topic, slug = slug) | |
| 47 | + | |
| 48 | + context['topic'] = topic | |
| 49 | + context['subject'] = topic.subject | |
| 50 | + | |
| 51 | + return context | |
| 52 | + | |
| 53 | + def get_success_url(self): | |
| 54 | + messages.success(self.request, _('Topic "%s" was created successfully!')%(self.object.name)) | |
| 55 | + | |
| 56 | + return reverse_lazy('subjects:view', kwargs = {'slug': self.object.topic.subject.slug}) | |
| 0 | 57 | \ No newline at end of file | ... | ... |