Commit 10d23f977451c58c9539655845b3425036f3ad29
Exists in
master
and in
3 other branches
Merge branch 'refactoring' of https://github.com/amadeusproject/amadeuslms into refactoring
Showing
33 changed files
with
739 additions
and
12 deletions
Show diff stats
amadeus/settings.py
amadeus/static/css/base/amadeus.css
@@ -73,7 +73,7 @@ a:focus { | @@ -73,7 +73,7 @@ a:focus { | ||
73 | /* side bar menu ends*/ | 73 | /* side bar menu ends*/ |
74 | 74 | ||
75 | /* category app starts */ | 75 | /* category app starts */ |
76 | -.category-panel > .panel-heading, .subject-panel > .panel-heading, .special-panel > .panel-heading, .topic-panel > .panel-heading { | 76 | +.category-panel > .panel-heading, .subject-panel > .panel-heading, .special-panel > .panel-heading, .topic-panel > .panel-heading, .group-panel > .panel-heading { |
77 | padding: 2px 0px; | 77 | padding: 2px 0px; |
78 | } | 78 | } |
79 | 79 |
amadeus/static/css/themes/green.css
@@ -59,6 +59,19 @@ a, a:focus, a:hover { | @@ -59,6 +59,19 @@ a, a:focus, a:hover { | ||
59 | background-color: #039BE5 !important; | 59 | background-color: #039BE5 !important; |
60 | } | 60 | } |
61 | 61 | ||
62 | +.group-panel > .panel-heading { | ||
63 | + background-color: #FFFFFF !important; | ||
64 | +} | ||
65 | + | ||
66 | +.group-panel .panel-title, .group-panel .category-header i, .group-panel .category-course-link { | ||
67 | + color: #000000 !important; | ||
68 | +} | ||
69 | + | ||
70 | +.group-panel-body { | ||
71 | + background: #FFFFFF !important; | ||
72 | + border-top: 1px solid #CCCCCC; | ||
73 | +} | ||
74 | + | ||
62 | .topic-panel > .panel-heading { | 75 | .topic-panel > .panel-heading { |
63 | background-color: #7BA5B9 !important; | 76 | background-color: #7BA5B9 !important; |
64 | } | 77 | } |
amadeus/static/js/course.js
@@ -193,4 +193,14 @@ $('.collapse').on('hide.bs.collapse', function (e) { | @@ -193,4 +193,14 @@ $('.collapse').on('hide.bs.collapse', function (e) { | ||
193 | }); | 193 | }); |
194 | } | 194 | } |
195 | } | 195 | } |
196 | -}); | ||
197 | \ No newline at end of file | 196 | \ No newline at end of file |
197 | +}); | ||
198 | + | ||
199 | +function delete_group(url) { | ||
200 | + $('.modal').remove(); | ||
201 | + | ||
202 | + $.get(url, function (modal) { | ||
203 | + $("#group-accordion").after(modal); | ||
204 | + | ||
205 | + $('.modal').modal('show'); | ||
206 | + }); | ||
207 | +} | ||
198 | \ No newline at end of file | 208 | \ No newline at end of file |
amadeus/urls.py
@@ -27,6 +27,7 @@ urlpatterns = [ | @@ -27,6 +27,7 @@ urlpatterns = [ | ||
27 | url(r'^$', index, name = 'home'), | 27 | url(r'^$', index, name = 'home'), |
28 | url(r'^categories/', include('categories.urls', namespace = 'categories')), | 28 | url(r'^categories/', include('categories.urls', namespace = 'categories')), |
29 | url(r'^subjects/', include('subjects.urls', namespace = 'subjects')), | 29 | url(r'^subjects/', include('subjects.urls', namespace = 'subjects')), |
30 | + url(r'^groups/', include('students_group.urls', namespace = 'groups')), | ||
30 | url(r'^topics/', include('topics.urls', namespace = 'topics')), | 31 | url(r'^topics/', include('topics.urls', namespace = 'topics')), |
31 | url(r'^mailsender/', include('mailsender.urls', namespace = 'mailsender')), | 32 | url(r'^mailsender/', include('mailsender.urls', namespace = 'mailsender')), |
32 | url(r'^security/', include('security.urls', namespace = 'security')), | 33 | url(r'^security/', include('security.urls', namespace = 'security')), |
@@ -0,0 +1,21 @@ | @@ -0,0 +1,21 @@ | ||
1 | +# -*- coding: utf-8 -*- | ||
2 | +# Generated by Django 1.10 on 2017-01-18 20:11 | ||
3 | +from __future__ import unicode_literals | ||
4 | + | ||
5 | +from django.conf import settings | ||
6 | +from django.db import migrations, models | ||
7 | + | ||
8 | + | ||
9 | +class Migration(migrations.Migration): | ||
10 | + | ||
11 | + dependencies = [ | ||
12 | + ('categories', '0006_auto_20170102_1856'), | ||
13 | + ] | ||
14 | + | ||
15 | + operations = [ | ||
16 | + migrations.AlterField( | ||
17 | + model_name='category', | ||
18 | + name='coordinators', | ||
19 | + field=models.ManyToManyField(blank=True, related_name='Coordenadores', to=settings.AUTH_USER_MODEL), | ||
20 | + ), | ||
21 | + ] |
@@ -0,0 +1,43 @@ | @@ -0,0 +1,43 @@ | ||
1 | +# coding=utf-8 | ||
2 | +from django import forms | ||
3 | +from django.utils.translation import ugettext_lazy as _ | ||
4 | + | ||
5 | +from subjects.models import Subject | ||
6 | + | ||
7 | +from .models import StudentsGroup | ||
8 | + | ||
9 | +class StudentsGroupForm(forms.ModelForm): | ||
10 | + subject = None | ||
11 | + | ||
12 | + def __init__(self, *args, **kwargs): | ||
13 | + super(StudentsGroupForm, self).__init__(*args, **kwargs) | ||
14 | + | ||
15 | + self.subject = kwargs['initial'].get('subject', None) | ||
16 | + | ||
17 | + if self.instance.id: | ||
18 | + self.subject = self.instance.subject | ||
19 | + | ||
20 | + self.fields['participants'].queryset = self.subject.students.all() | ||
21 | + | ||
22 | + def clean_name(self): | ||
23 | + name = self.cleaned_data.get('name', '') | ||
24 | + | ||
25 | + if self.instance.id: | ||
26 | + same_name = self.subject.group_subject.filter(name__unaccent__iexact = name).exclude(id = self.instance.id).count() | ||
27 | + else: | ||
28 | + same_name = self.subject.group_subject.filter(name__unaccent__iexact = name).count() | ||
29 | + | ||
30 | + if same_name > 0: | ||
31 | + self._errors['name'] = [_('This subject already has a group with this name')] | ||
32 | + | ||
33 | + return ValueError | ||
34 | + | ||
35 | + return name | ||
36 | + | ||
37 | + class Meta: | ||
38 | + model = StudentsGroup | ||
39 | + fields = ['name', 'description', 'participants'] | ||
40 | + widgets = { | ||
41 | + 'description': forms.Textarea, | ||
42 | + 'participants': forms.SelectMultiple, | ||
43 | + } | ||
0 | \ No newline at end of file | 44 | \ No newline at end of file |
@@ -0,0 +1,38 @@ | @@ -0,0 +1,38 @@ | ||
1 | +# -*- coding: utf-8 -*- | ||
2 | +# Generated by Django 1.10 on 2017-01-18 20:11 | ||
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 | + initial = True | ||
14 | + | ||
15 | + dependencies = [ | ||
16 | + migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
17 | + ('subjects', '0012_auto_20170112_1408'), | ||
18 | + ] | ||
19 | + | ||
20 | + operations = [ | ||
21 | + migrations.CreateModel( | ||
22 | + name='StudentsGroup', | ||
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 | + ('description', models.TextField(blank=True, verbose_name='Description')), | ||
28 | + ('create_date', models.DateTimeField(auto_now_add=True, verbose_name='Create Date')), | ||
29 | + ('last_update', models.DateTimeField(auto_now=True, verbose_name='Last Update')), | ||
30 | + ('participants', models.ManyToManyField(blank=True, related_name='group_participants', to=settings.AUTH_USER_MODEL, verbose_name='Participants')), | ||
31 | + ('subject', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='group_subject', to='subjects.Subject', verbose_name='Subject')), | ||
32 | + ], | ||
33 | + options={ | ||
34 | + 'verbose_name': 'Students Group', | ||
35 | + 'verbose_name_plural': 'Students Groups', | ||
36 | + }, | ||
37 | + ), | ||
38 | + ] |
@@ -0,0 +1,17 @@ | @@ -0,0 +1,17 @@ | ||
1 | +# -*- coding: utf-8 -*- | ||
2 | +# Generated by Django 1.10 on 2017-01-18 21:00 | ||
3 | +from __future__ import unicode_literals | ||
4 | + | ||
5 | +from django.db import migrations | ||
6 | + | ||
7 | +from django.contrib.postgres.operations import UnaccentExtension | ||
8 | + | ||
9 | +class Migration(migrations.Migration): | ||
10 | + | ||
11 | + dependencies = [ | ||
12 | + ('students_group', '0001_initial'), | ||
13 | + ] | ||
14 | + | ||
15 | + operations = [ | ||
16 | + UnaccentExtension() | ||
17 | + ] |
@@ -0,0 +1,23 @@ | @@ -0,0 +1,23 @@ | ||
1 | +from django.db import models | ||
2 | +from autoslug.fields import AutoSlugField | ||
3 | +from django.utils.translation import ugettext_lazy as _ | ||
4 | + | ||
5 | +from subjects.models import Subject | ||
6 | +from users.models import User | ||
7 | + | ||
8 | +class StudentsGroup(models.Model): | ||
9 | + name = models.CharField(_('Name'), max_length = 200) | ||
10 | + slug = AutoSlugField(_("Slug"), populate_from = 'name', unique = True) | ||
11 | + description = models.TextField(_('Description'), blank = True) | ||
12 | + subject = models.ForeignKey(Subject, verbose_name = _('Subject'), related_name = 'group_subject', null = True) | ||
13 | + participants = models.ManyToManyField(User, verbose_name = _('Participants'), related_name = 'group_participants', blank = True) | ||
14 | + create_date = models.DateTimeField(_('Create Date'), auto_now_add = True) | ||
15 | + last_update = models.DateTimeField(_('Last Update'), auto_now = True) | ||
16 | + | ||
17 | + class Meta: | ||
18 | + verbose_name = _('Students Group') | ||
19 | + verbose_name_plural = _('Students Groups') | ||
20 | + ordering = ['name'] | ||
21 | + | ||
22 | + def __str__(self): | ||
23 | + return self.name | ||
0 | \ No newline at end of file | 24 | \ No newline at end of file |
@@ -0,0 +1,113 @@ | @@ -0,0 +1,113 @@ | ||
1 | +{% load widget_tweaks static i18n %} | ||
2 | + | ||
3 | +<form method="post" action="" enctype="multipart/form-data"> | ||
4 | + {% csrf_token %} | ||
5 | + {% for field in form %} | ||
6 | + {% if field.auto_id == 'id_participants' %} | ||
7 | + <div class="panel-group" id="professors_accordion" role="tablist" aria-multiselectable="true"> | ||
8 | + <div class="panel panel-info"> | ||
9 | + <div class="panel-heading"> | ||
10 | + <div class="row"> | ||
11 | + <div class="col-md-12"> | ||
12 | + <a data-parent="#professors_accordion" data-toggle="collapse" href="#participants"> | ||
13 | + <h4 class="panel-title"> | ||
14 | + <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="{{ field.auto_id }}">{{ field.label }}</label> | ||
15 | + </h4> | ||
16 | + </a> | ||
17 | + </div> | ||
18 | + </div> | ||
19 | + </div> | ||
20 | + <div id="participants" class="panel-collapse collapse"> | ||
21 | + <p><em>{% trans 'Attribute students to group' %}:</em></p> | ||
22 | + {% render_field field class='form-control' %} | ||
23 | + </div> | ||
24 | + </div> | ||
25 | + </div> | ||
26 | + {% else %} | ||
27 | + <div class="form-group {% if form.has_error %} has-error {% endif %} is-fileinput"> | ||
28 | + <label for="{{ field.auto_id }}" class="control-label">{{ field.label }}</label> | ||
29 | + | ||
30 | + {% if field.auto_id == 'id_description' %} | ||
31 | + {% render_field field class='form-control text_wysiwyg' %} | ||
32 | + {% else %} | ||
33 | + {% render_field field class='form-control' %} | ||
34 | + {% endif %} | ||
35 | + </div> | ||
36 | + {% endif %} | ||
37 | + | ||
38 | + <span class="help-block">{{ field.help_text }}</span> | ||
39 | + | ||
40 | + {% if field.errors %} | ||
41 | + <div class="row"> | ||
42 | + </br> | ||
43 | + <div class="alert alert-danger alert-dismissible" role="alert"> | ||
44 | + <button type="button" class="close" data-dismiss="alert" aria-label="Close"> | ||
45 | + <span aria-hidden="true">×</span> | ||
46 | + </button> | ||
47 | + <ul> | ||
48 | + {% for error in field.errors %} | ||
49 | + <li>{{ error }}</li> | ||
50 | + {% endfor %} | ||
51 | + </ul> | ||
52 | + </div> | ||
53 | + </div> | ||
54 | + {% endif %} | ||
55 | + {% endfor %} | ||
56 | + <div class="row text-center"> | ||
57 | + <input type="submit" value="{% trans 'Save' %}" class="btn btn-success btn-raised" /> | ||
58 | + </div> | ||
59 | +</form> | ||
60 | + | ||
61 | +<script type="text/javascript"> | ||
62 | + $('#id_participants').multiSelect({ | ||
63 | + selectableHeader: "<input type='text' class='search-input category-search-users' autocomplete='off' placeholder=' '>", | ||
64 | + selectionHeader: "<input type='text' class='search-input category-search-users' autocomplete='off' placeholder=''>", | ||
65 | + afterInit: function(ms){ | ||
66 | + var that = this, | ||
67 | + $selectableSearch = that.$selectableUl.prev(), | ||
68 | + $selectionSearch = that.$selectionUl.prev(), | ||
69 | + selectableSearchString = '#'+that.$container.attr('id')+' .ms-elem-selectable:not(.ms-selected)', | ||
70 | + selectionSearchString = '#'+that.$container.attr('id')+' .ms-elem-selection.ms-selected'; | ||
71 | + | ||
72 | + that.qs1 = $selectableSearch.quicksearch(selectableSearchString) | ||
73 | + .on('keydown', function(e){ | ||
74 | + if (e.which === 40){ | ||
75 | + that.$selectableUl.focus(); | ||
76 | + return false; | ||
77 | + } | ||
78 | + }); | ||
79 | + | ||
80 | + that.qs2 = $selectionSearch.quicksearch(selectionSearchString) | ||
81 | + .on('keydown', function(e){ | ||
82 | + if (e.which == 40){ | ||
83 | + that.$selectionUl.focus(); | ||
84 | + return false; | ||
85 | + } | ||
86 | + }); | ||
87 | + }, | ||
88 | + afterSelect: function(){ | ||
89 | + this.qs1.cache(); | ||
90 | + this.qs2.cache(); | ||
91 | + }, | ||
92 | + afterDeselect: function(){ | ||
93 | + this.qs1.cache(); | ||
94 | + this.qs2.cache(); | ||
95 | + } | ||
96 | + });// Used to create multi-select css style | ||
97 | + | ||
98 | + $('.collapse').on('show.bs.collapse', function (e) { | ||
99 | + if($(this).is(e.target)){ | ||
100 | + var btn = $(this).parent().find('.fa-angle-right'); | ||
101 | + | ||
102 | + btn.switchClass("fa-angle-right", "fa-angle-down", 250, "easeInOutQuad"); | ||
103 | + } | ||
104 | + }); | ||
105 | + | ||
106 | + $('.collapse').on('hide.bs.collapse', function (e) { | ||
107 | + if($(this).is(e.target)){ | ||
108 | + var btn = $(this).parent().find('.fa-angle-down'); | ||
109 | + | ||
110 | + btn.switchClass("fa-angle-down", "fa-angle-right", 250, "easeInOutQuad"); | ||
111 | + } | ||
112 | + }); | ||
113 | +</script> | ||
0 | \ No newline at end of file | 114 | \ No newline at end of file |
@@ -0,0 +1,30 @@ | @@ -0,0 +1,30 @@ | ||
1 | +{% extends 'groups/index.html' %} | ||
2 | + | ||
3 | +{% load i18n django_bootstrap_breadcrumbs %} | ||
4 | + | ||
5 | +{% block breadcrumbs %} | ||
6 | + {{ block.super }} | ||
7 | + | ||
8 | + {% if group %} | ||
9 | + {% trans 'Replicate: ' as bread %} | ||
10 | + | ||
11 | + {% with bread|add:group.name as bread_slug %} | ||
12 | + {% breadcrumb bread_slug 'groups:replicate' subject.slug group.slug %} | ||
13 | + {% endwith %} | ||
14 | + {% else %} | ||
15 | + {% trans 'Create Group' as bread %} | ||
16 | + {% breadcrumb bread 'groups:create' subject.slug %} | ||
17 | + {% endif %} | ||
18 | +{% endblock %} | ||
19 | + | ||
20 | +{% block content %} | ||
21 | + <div class="card"> | ||
22 | + <div class="card-content"> | ||
23 | + <div class="card-body"> | ||
24 | + {% include 'groups/_form.html' %} | ||
25 | + </div> | ||
26 | + </div> | ||
27 | + </div> | ||
28 | + <br clear="all" /> | ||
29 | + <br clear="all" /> | ||
30 | +{% endblock %} |
@@ -0,0 +1,23 @@ | @@ -0,0 +1,23 @@ | ||
1 | +{% load i18n %} | ||
2 | + | ||
3 | +<div class="modal fade" id="topic" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> | ||
4 | + <div class="modal-dialog" role="document"> | ||
5 | + <div class="modal-content"> | ||
6 | + <div class="modal-body"> | ||
7 | + <form id="delete_form" action="{% url 'groups:delete' group.slug %}" method="post"> | ||
8 | + {% csrf_token %} | ||
9 | + <h3>{% trans 'Are you sure you want delete the group' %}: {{ group }}?</h3> | ||
10 | + <p>{% trans 'With this action the group will no longer exist but your participants will still be attached to the subject.' %}</p> | ||
11 | + </form> | ||
12 | + </div> | ||
13 | + <div class="modal-footer"> | ||
14 | + <div class="pull-right"> | ||
15 | + <button type="button" class="btn btn-default btn-raised" data-dismiss="modal">{% trans "Close" %}</button> | ||
16 | + </div> | ||
17 | + <div class="pull-left"> | ||
18 | + <button type="submit" form="delete_form" class="btn btn-success btn-raised">{% trans "Delete" %}</button> | ||
19 | + </div> | ||
20 | + </div> | ||
21 | + </div> | ||
22 | + </div> | ||
23 | +</div> | ||
0 | \ No newline at end of file | 24 | \ No newline at end of file |
@@ -0,0 +1,89 @@ | @@ -0,0 +1,89 @@ | ||
1 | +{% extends 'subjects/view.html' %} | ||
2 | + | ||
3 | +{% load static i18n pagination %} | ||
4 | +{% load django_bootstrap_breadcrumbs %} | ||
5 | + | ||
6 | +{% block javascript%} | ||
7 | + {{ block.super }} | ||
8 | +{% endblock%} | ||
9 | + | ||
10 | +{% block breadcrumbs %} | ||
11 | + {{ block.super }} | ||
12 | + | ||
13 | + {% trans 'Groups' as bread %} | ||
14 | + {% breadcrumb bread 'groups:index' subject.slug %} | ||
15 | +{% endblock %} | ||
16 | + | ||
17 | +{% block content %} | ||
18 | + {% if messages %} | ||
19 | + {% for message in messages %} | ||
20 | + <div class="alert alert-{{ message.tags }} alert-dismissible" role="alert"> | ||
21 | + <button type="button" class="close" data-dismiss="alert" aria-label="Close"> | ||
22 | + <span aria-hidden="true">×</span> | ||
23 | + </button> | ||
24 | + <p>{{ message }}</p> | ||
25 | + </div> | ||
26 | + {% endfor %} | ||
27 | + {% endif %} | ||
28 | + | ||
29 | + <div class='row'> | ||
30 | + <div class="col-md-12 text-center"> | ||
31 | + <a href="{% url 'groups:create' subject.slug %}" class="btn btn-raised btn-success">{% trans "Create Group" %}</a> | ||
32 | + </div> | ||
33 | + </div> | ||
34 | + | ||
35 | + <div class="col-md-12 cards-content"> | ||
36 | + <div class="panel-group" id="group-accordion" role="tablist" aria-multiselectable="true"> | ||
37 | + {% for group in groups %} | ||
38 | + <div class="panel panel-info group-panel"> | ||
39 | + <div class="panel-heading"> | ||
40 | + <div class="row"> | ||
41 | + <div class="col-md-12 category-header"> | ||
42 | + <h4 class="panel-title"> | ||
43 | + <a class="category-course-link pull-left" data-parent="#group-accordion" data-toggle="collapse" href="#{{group.slug}}"> | ||
44 | + <button class="btn btn-default btn-xs text-center cat-selector"><i class="fa fa-angle-right fa-2x" aria-hidden="true"></i></button> {{ group }} ({{ group.participants.count }}) | ||
45 | + </a> | ||
46 | + </h4> | ||
47 | + | ||
48 | + <div class="col-md-5 pull-right category-card-items"> | ||
49 | + <a href="" id="moreActions" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> | ||
50 | + <i class="fa fa-ellipsis-v" aria-hidden="true"></i> | ||
51 | + </a> | ||
52 | + <ul class="dropdown-menu pull-right" aria-labelledby="moreActions"> | ||
53 | + <li> | ||
54 | + <a href="{% url 'groups:replicate' subject.slug group.slug %}"> | ||
55 | + <i class="fa fa-files-o" aria-hidden="true"></i> {% trans 'Replicate' %} | ||
56 | + </a> | ||
57 | + </li> | ||
58 | + <li><a href="{% url 'groups:update' subject.slug group.slug %}"><i class="fa fa-pencil fa-fw" aria-hidden="true"></i> {% trans 'Edit' %}</a></li> | ||
59 | + <li><a href="javascript:delete_group('{% url 'groups:delete' group.slug %}')"><i class="fa fa-trash fa-fw" aria-hidden="true"></i> {% trans 'Remove' %}</a></li> | ||
60 | + </ul> | ||
61 | + </div> | ||
62 | + </div> | ||
63 | + </div> | ||
64 | + </div> | ||
65 | + <div id="{{ group.slug }}" class="panel-collapse panel-body collapse category-panel-content group-panel-body"> | ||
66 | + {% autoescape off %} | ||
67 | + {{ group.description }} | ||
68 | + {% endautoescape %} | ||
69 | + | ||
70 | + <h3>{% trans 'Participant(s)' %}: </h3> | ||
71 | + | ||
72 | + <ul class="list-inline"> | ||
73 | + {% for user in group.participants.all %} | ||
74 | + <li class="list-inline-item text-center col-lg-2 col-md-2 col-sm-2 col-xs-3"> | ||
75 | + <img src="{{ user.image_url }}" class="img-responsive" /> | ||
76 | + <p><small>{{ user }}</small></p> | ||
77 | + </li> | ||
78 | + {% endfor %} | ||
79 | + </ul> | ||
80 | + </div> | ||
81 | + </div> | ||
82 | + {% endfor %} | ||
83 | + | ||
84 | + {% pagination request paginator page_obj %} | ||
85 | + </div> | ||
86 | + </div> | ||
87 | + | ||
88 | + <script type="text/javascript" src="{% static 'js/course.js' %}"></script> | ||
89 | +{% endblock %} | ||
0 | \ No newline at end of file | 90 | \ No newline at end of file |
@@ -0,0 +1,25 @@ | @@ -0,0 +1,25 @@ | ||
1 | +{% extends 'groups/index.html' %} | ||
2 | + | ||
3 | +{% load i18n django_bootstrap_breadcrumbs %} | ||
4 | + | ||
5 | +{% block breadcrumbs %} | ||
6 | + {{ block.super }} | ||
7 | + | ||
8 | + {% trans 'Update: ' as bread %} | ||
9 | + | ||
10 | + {% with bread|add:group.name as bread_slug %} | ||
11 | + {% breadcrumb bread_slug 'groups:update' subject.slug group.slug %} | ||
12 | + {% endwith %} | ||
13 | +{% endblock %} | ||
14 | + | ||
15 | +{% block content %} | ||
16 | + <div class="card"> | ||
17 | + <div class="card-content"> | ||
18 | + <div class="card-body"> | ||
19 | + {% include 'groups/_form.html' %} | ||
20 | + </div> | ||
21 | + </div> | ||
22 | + </div> | ||
23 | + <br clear="all" /> | ||
24 | + <br clear="all" /> | ||
25 | +{% endblock %} |
@@ -0,0 +1,10 @@ | @@ -0,0 +1,10 @@ | ||
1 | +from django.conf.urls import url | ||
2 | +from . import views | ||
3 | + | ||
4 | +urlpatterns = [ | ||
5 | + url(r'^(?P<slug>[\w_-]+)/$', views.IndexView.as_view(), name='index'), | ||
6 | + url(r'^create/(?P<slug>[\w_-]+)/$', views.CreateView.as_view(), name='create'), | ||
7 | + url(r'^update/(?P<sub_slug>[\w_-]+)/(?P<slug>[\w_-]+)/$', views.UpdateView.as_view(), name='update'), | ||
8 | + url(r'^replicate/(?P<slug>[\w_-]+)/(?P<group_slug>[\w_-]+)/$', views.CreateView.as_view(), name='replicate'), | ||
9 | + url(r'^delete/(?P<slug>[\w_-]+)/$', views.DeleteView.as_view(), name = 'delete'), | ||
10 | +] | ||
0 | \ No newline at end of file | 11 | \ No newline at end of file |
@@ -0,0 +1,174 @@ | @@ -0,0 +1,174 @@ | ||
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.http import JsonResponse | ||
5 | +from django.core.urlresolvers import reverse, reverse_lazy | ||
6 | +from django.utils.translation import ugettext_lazy as _ | ||
7 | +from django.contrib.auth.mixins import LoginRequiredMixin | ||
8 | + | ||
9 | +from amadeus.permissions import has_subject_permissions | ||
10 | + | ||
11 | +from subjects.models import Subject | ||
12 | + | ||
13 | +from .models import StudentsGroup | ||
14 | +from .forms import StudentsGroupForm | ||
15 | + | ||
16 | +class IndexView(LoginRequiredMixin, generic.ListView): | ||
17 | + login_url = reverse_lazy("users:login") | ||
18 | + redirect_field_name = 'next' | ||
19 | + | ||
20 | + model = StudentsGroup | ||
21 | + context_object_name = 'groups' | ||
22 | + template_name = 'groups/index.html' | ||
23 | + paginate_by = 10 | ||
24 | + | ||
25 | + def dispatch(self, request, *args, **kwargs): | ||
26 | + slug = self.kwargs.get('slug', '') | ||
27 | + subject = get_object_or_404(Subject, slug = slug) | ||
28 | + | ||
29 | + if not has_subject_permissions(request.user, subject): | ||
30 | + return redirect(reverse_lazy('subjects:home')) | ||
31 | + | ||
32 | + return super(IndexView, self).dispatch(request, *args, **kwargs) | ||
33 | + | ||
34 | + def get_queryset(self): | ||
35 | + slug = self.kwargs.get('slug', '') | ||
36 | + subject = get_object_or_404(Subject, slug = slug) | ||
37 | + | ||
38 | + return StudentsGroup.objects.filter(subject = subject) | ||
39 | + | ||
40 | + def get_context_data(self, **kwargs): | ||
41 | + context = super(IndexView, self).get_context_data(**kwargs) | ||
42 | + | ||
43 | + slug = self.kwargs.get('slug', '') | ||
44 | + subject = get_object_or_404(Subject, slug = slug) | ||
45 | + | ||
46 | + context['title'] = _('Students Groups') | ||
47 | + context['subject'] = subject | ||
48 | + | ||
49 | + return context | ||
50 | + | ||
51 | +class CreateView(LoginRequiredMixin, generic.edit.CreateView): | ||
52 | + login_url = reverse_lazy("users:login") | ||
53 | + redirect_field_name = 'next' | ||
54 | + | ||
55 | + template_name = 'groups/create.html' | ||
56 | + form_class = StudentsGroupForm | ||
57 | + | ||
58 | + def dispatch(self, request, *args, **kwargs): | ||
59 | + slug = self.kwargs.get('slug', '') | ||
60 | + subject = get_object_or_404(Subject, slug = slug) | ||
61 | + | ||
62 | + if not has_subject_permissions(request.user, subject): | ||
63 | + return redirect(reverse_lazy('subjects:home')) | ||
64 | + | ||
65 | + return super(CreateView, self).dispatch(request, *args, **kwargs) | ||
66 | + | ||
67 | + def get_initial(self): | ||
68 | + initial = super(CreateView, self).get_initial() | ||
69 | + | ||
70 | + slug = self.kwargs.get('slug', '') | ||
71 | + | ||
72 | + initial['subject'] = get_object_or_404(Subject, slug = slug) | ||
73 | + | ||
74 | + if self.kwargs.get('group_slug'): | ||
75 | + group = get_object_or_404(StudentsGroup, slug = self.kwargs['group_slug']) | ||
76 | + initial = initial.copy() | ||
77 | + initial['description'] = group.description | ||
78 | + initial['name'] = group.name | ||
79 | + initial['participants'] = group.participants.all() | ||
80 | + | ||
81 | + return initial | ||
82 | + | ||
83 | + def form_valid(self, form): | ||
84 | + self.object = form.save(commit = False) | ||
85 | + | ||
86 | + slug = self.kwargs.get('slug', '') | ||
87 | + subject = get_object_or_404(Subject, slug = slug) | ||
88 | + | ||
89 | + self.object.subject = subject | ||
90 | + | ||
91 | + self.object.save() | ||
92 | + | ||
93 | + return super(CreateView, self).form_valid(form) | ||
94 | + | ||
95 | + def get_context_data(self, **kwargs): | ||
96 | + context = super(CreateView, self).get_context_data(**kwargs) | ||
97 | + | ||
98 | + context['title'] = _('Create Topic') | ||
99 | + | ||
100 | + slug = self.kwargs.get('slug', '') | ||
101 | + subject = get_object_or_404(Subject, slug = slug) | ||
102 | + | ||
103 | + context['subject'] = subject | ||
104 | + | ||
105 | + if self.kwargs.get('group_slug'): | ||
106 | + group = get_object_or_404(StudentsGroup, slug = self.kwargs['group_slug']) | ||
107 | + | ||
108 | + context['title'] = _('Replicate Group') | ||
109 | + | ||
110 | + context['group'] = group | ||
111 | + | ||
112 | + return context | ||
113 | + | ||
114 | + def get_success_url(self): | ||
115 | + messages.success(self.request, _('The group "%s" was created successfully!')%(self.object.name)) | ||
116 | + | ||
117 | + return reverse_lazy('groups:index', kwargs = {'slug': self.object.subject.slug}) | ||
118 | + | ||
119 | +class UpdateView(LoginRequiredMixin, generic.UpdateView): | ||
120 | + login_url = reverse_lazy("users:login") | ||
121 | + redirect_field_name = 'next' | ||
122 | + | ||
123 | + template_name = 'groups/update.html' | ||
124 | + model = StudentsGroup | ||
125 | + form_class = StudentsGroupForm | ||
126 | + context_object_name = 'group' | ||
127 | + | ||
128 | + def dispatch(self, request, *args, **kwargs): | ||
129 | + slug = self.kwargs.get('sub_slug', '') | ||
130 | + subject = get_object_or_404(Subject, slug = slug) | ||
131 | + | ||
132 | + if not has_subject_permissions(request.user, subject): | ||
133 | + return redirect(reverse_lazy('subjects:home')) | ||
134 | + | ||
135 | + return super(UpdateView, self).dispatch(request, *args, **kwargs) | ||
136 | + | ||
137 | + def get_context_data(self, **kwargs): | ||
138 | + context = super(UpdateView, self).get_context_data(**kwargs) | ||
139 | + | ||
140 | + context['title'] = _('Update Group') | ||
141 | + | ||
142 | + slug = self.kwargs.get('sub_slug', '') | ||
143 | + subject = get_object_or_404(Subject, slug = slug) | ||
144 | + | ||
145 | + context['subject'] = subject | ||
146 | + | ||
147 | + return context | ||
148 | + | ||
149 | + def get_success_url(self): | ||
150 | + messages.success(self.request, _('The group "%s" was updated successfully!')%(self.object.name)) | ||
151 | + | ||
152 | + return reverse_lazy('groups:index', kwargs = {'slug': self.object.subject.slug}) | ||
153 | + | ||
154 | +class DeleteView(LoginRequiredMixin, generic.DeleteView): | ||
155 | + login_url = reverse_lazy("users:login") | ||
156 | + redirect_field_name = 'next' | ||
157 | + | ||
158 | + template_name = 'groups/delete.html' | ||
159 | + model = StudentsGroup | ||
160 | + context_object_name = 'group' | ||
161 | + | ||
162 | + def dispatch(self, request, *args, **kwargs): | ||
163 | + slug = self.kwargs.get('slug', '') | ||
164 | + group = get_object_or_404(StudentsGroup, slug = slug) | ||
165 | + | ||
166 | + if not has_subject_permissions(request.user, group.subject): | ||
167 | + return redirect(reverse_lazy('subjects:home')) | ||
168 | + | ||
169 | + return super(DeleteView, self).dispatch(request, *args, **kwargs) | ||
170 | + | ||
171 | + def get_success_url(self): | ||
172 | + messages.success(self.request, _('The group "%s" was removed successfully!')%(self.object.name)) | ||
173 | + | ||
174 | + return reverse_lazy('groups:index', kwargs = {'slug': self.object.subject.slug}) | ||
0 | \ No newline at end of file | 175 | \ No newline at end of file |
subjects/templates/subjects/list.html
@@ -90,6 +90,7 @@ | @@ -90,6 +90,7 @@ | ||
90 | <div id="{{category.slug}}" class="panel-collapse panel-body collapse category-panel-content"> | 90 | <div id="{{category.slug}}" class="panel-collapse panel-body collapse category-panel-content"> |
91 | <input type="hidden" class="log_url" value="{% url 'categories:view_log' category.id %}" /> | 91 | <input type="hidden" class="log_url" value="{% url 'categories:view_log' category.id %}" /> |
92 | <input type="hidden" class="log_id" value="" /> | 92 | <input type="hidden" class="log_id" value="" /> |
93 | + | ||
93 | {% if category.coordinators.all|length > 0 %} | 94 | {% if category.coordinators.all|length > 0 %} |
94 | <h4><b>{% trans "Coordinator(s) " %}: </b> | 95 | <h4><b>{% trans "Coordinator(s) " %}: </b> |
95 | {{ category.coordinators.all|join:', ' }} | 96 | {{ category.coordinators.all|join:', ' }} |
@@ -193,4 +194,17 @@ | @@ -193,4 +194,17 @@ | ||
193 | <div id="modal_subject"></div> | 194 | <div id="modal_subject"></div> |
194 | 195 | ||
195 | <script type="text/javascript" src="{% static 'js/course.js' %}"></script> | 196 | <script type="text/javascript" src="{% static 'js/course.js' %}"></script> |
197 | + <script type="text/javascript"> | ||
198 | + $(function (){ | ||
199 | + var cat_slug = "{{ cat_slug }}" | ||
200 | + | ||
201 | + if (cat_slug != "") { | ||
202 | + $("#" + cat_slug).collapse('show'); | ||
203 | + | ||
204 | + $('html, body').animate({ | ||
205 | + scrollTop: $("#" + cat_slug).parent().offset().top | ||
206 | + }, 2000); | ||
207 | + } | ||
208 | + }); | ||
209 | + </script> | ||
196 | {% endblock %} | 210 | {% endblock %} |
subjects/templates/subjects/subject_card.html
@@ -18,6 +18,7 @@ | @@ -18,6 +18,7 @@ | ||
18 | <ul class="dropdown-menu pull-right" aria-labelledby="moreActions"> | 18 | <ul class="dropdown-menu pull-right" aria-labelledby="moreActions"> |
19 | <li><a href="{% url 'subjects:replicate' subject.slug %}"><i class="fa fa-files-o fa-fw" aria-hidden="true"></i>{% trans 'Replicate' %}</a></li> | 19 | <li><a href="{% url 'subjects:replicate' subject.slug %}"><i class="fa fa-files-o fa-fw" aria-hidden="true"></i>{% trans 'Replicate' %}</a></li> |
20 | <li><a href="{% url 'subjects:update' subject.slug %}"><i class="fa fa-pencil fa-fw" aria-hidden="true"></i>{% trans 'Edit' %}</a></li> | 20 | <li><a href="{% url 'subjects:update' subject.slug %}"><i class="fa fa-pencil fa-fw" aria-hidden="true"></i>{% trans 'Edit' %}</a></li> |
21 | + <li><a href="{% url 'groups:index' subject.slug %}"><i class="fa fa-group fa-fw" aria-hidden="true"></i>{% trans 'Groups' %}</a></li> | ||
21 | <li><a href="javascript:delete_subject.get('{% url 'subjects:delete' subject.slug %}?view=index','#subject','#modal_subject')"><i class="fa fa-trash fa-fw" aria-hidden="true"></i> {% trans 'Remove' %}</a></li> | 22 | <li><a href="javascript:delete_subject.get('{% url 'subjects:delete' subject.slug %}?view=index','#subject','#modal_subject')"><i class="fa fa-trash fa-fw" aria-hidden="true"></i> {% trans 'Remove' %}</a></li> |
22 | </ul> | 23 | </ul> |
23 | {% endif %} | 24 | {% endif %} |
subjects/templates/subjects/view.html
@@ -9,7 +9,7 @@ | @@ -9,7 +9,7 @@ | ||
9 | 9 | ||
10 | {% block breadcrumbs %} | 10 | {% block breadcrumbs %} |
11 | {{ block.super }} | 11 | {{ block.super }} |
12 | - {% breadcrumb subject.category 'subjects:index' %} | 12 | + {% breadcrumb subject.category 'subjects:cat_view' subject.category.slug %} |
13 | {% breadcrumb subject 'subjects:view' subject.slug %} | 13 | {% breadcrumb subject 'subjects:view' subject.slug %} |
14 | {% endblock %} | 14 | {% endblock %} |
15 | 15 | ||
@@ -48,6 +48,7 @@ | @@ -48,6 +48,7 @@ | ||
48 | <ul class="dropdown-menu pull-right" aria-labelledby="moreActions"> | 48 | <ul class="dropdown-menu pull-right" aria-labelledby="moreActions"> |
49 | <li><a href="{% url 'subjects:replicate' subject.slug %}"><i class="fa fa-files-o fa-fw" aria-hidden="true"></i>{% trans 'Replicate' %}</a></li> | 49 | <li><a href="{% url 'subjects:replicate' subject.slug %}"><i class="fa fa-files-o fa-fw" aria-hidden="true"></i>{% trans 'Replicate' %}</a></li> |
50 | <li><a href="{% url 'subjects:update' subject.slug %}"><i class="fa fa-pencil fa-fw" aria-hidden="true"></i>{% trans 'Edit' %}</a></li> | 50 | <li><a href="{% url 'subjects:update' subject.slug %}"><i class="fa fa-pencil fa-fw" aria-hidden="true"></i>{% trans 'Edit' %}</a></li> |
51 | + <li><a href="{% url 'groups:index' subject.slug %}"><i class="fa fa-group fa-fw" aria-hidden="true"></i>{% trans 'Groups' %}</a></li> | ||
51 | <li><a href="javascript:delete_subject.get('{% url 'subjects:delete' subject.slug %}?view=index','#subject','#modal_subject')"><i class="fa fa-trash fa-fw" aria-hidden="true"></i> {% trans 'Remove' %}</a></li> | 52 | <li><a href="javascript:delete_subject.get('{% url 'subjects:delete' subject.slug %}?view=index','#subject','#modal_subject')"><i class="fa fa-trash fa-fw" aria-hidden="true"></i> {% trans 'Remove' %}</a></li> |
52 | </ul> | 53 | </ul> |
53 | {% endif %} | 54 | {% endif %} |
subjects/urls.py
@@ -4,6 +4,7 @@ from . import views | @@ -4,6 +4,7 @@ from . import views | ||
4 | urlpatterns = [ | 4 | urlpatterns = [ |
5 | url(r'^home/$', views.HomeView.as_view(), name='home'), | 5 | url(r'^home/$', views.HomeView.as_view(), name='home'), |
6 | url(r'^$', views.IndexView.as_view(), name='index'), | 6 | url(r'^$', views.IndexView.as_view(), name='index'), |
7 | + url(r'^category/(?P<slug>[\w_-]+)/$', views.IndexView.as_view(), name='cat_view'), | ||
7 | url(r'^(?P<option>[\w_-]+)/$', views.IndexView.as_view(), name='index'), | 8 | url(r'^(?P<option>[\w_-]+)/$', views.IndexView.as_view(), name='index'), |
8 | url(r'^create/(?P<slug>[\w_-]+)/$', views.SubjectCreateView.as_view(), name='create'), | 9 | url(r'^create/(?P<slug>[\w_-]+)/$', views.SubjectCreateView.as_view(), name='create'), |
9 | url(r'^replicate/(?P<subject_slug>[\w_-]+)/$', views.SubjectCreateView.as_view(), name='replicate'), | 10 | url(r'^replicate/(?P<subject_slug>[\w_-]+)/$', views.SubjectCreateView.as_view(), name='replicate'), |
subjects/utils.py
@@ -18,6 +18,17 @@ def has_professor_profile(user, category): | @@ -18,6 +18,17 @@ def has_professor_profile(user, category): | ||
18 | 18 | ||
19 | return False | 19 | return False |
20 | 20 | ||
21 | +def get_category_page(categories, slug, per_page): | ||
22 | + total = 1 | ||
23 | + | ||
24 | + for category in categories: | ||
25 | + if category.slug == slug: | ||
26 | + return total / per_page + 1 | ||
27 | + | ||
28 | + total += 1 | ||
29 | + | ||
30 | + return 1 | ||
31 | + | ||
21 | def count_subjects( user, all_subs = True): | 32 | def count_subjects( user, all_subs = True): |
22 | total = 0 | 33 | total = 0 |
23 | pk = user.pk | 34 | pk = user.pk |
subjects/views.py
@@ -26,7 +26,7 @@ import time | @@ -26,7 +26,7 @@ import time | ||
26 | import datetime | 26 | import datetime |
27 | from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger | 27 | from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger |
28 | from .forms import CreateSubjectForm | 28 | from .forms import CreateSubjectForm |
29 | -from .utils import has_student_profile, has_professor_profile, count_subjects | 29 | +from .utils import has_student_profile, has_professor_profile, count_subjects, get_category_page |
30 | from users.models import User | 30 | from users.models import User |
31 | 31 | ||
32 | 32 | ||
@@ -55,6 +55,7 @@ class HomeView(LoginRequiredMixin, ListView): | @@ -55,6 +55,7 @@ class HomeView(LoginRequiredMixin, ListView): | ||
55 | #bringing users | 55 | #bringing users |
56 | tags = Tag.objects.all() | 56 | tags = Tag.objects.all() |
57 | context['tags'] = tags | 57 | context['tags'] = tags |
58 | + | ||
58 | return context | 59 | return context |
59 | 60 | ||
60 | 61 | ||
@@ -67,8 +68,7 @@ class IndexView(LoginRequiredMixin, ListView): | @@ -67,8 +68,7 @@ class IndexView(LoginRequiredMixin, ListView): | ||
67 | context_object_name = 'categories' | 68 | context_object_name = 'categories' |
68 | paginate_by = 10 | 69 | paginate_by = 10 |
69 | 70 | ||
70 | - def get_queryset(self): | ||
71 | - | 71 | + def get_queryset(self): |
72 | if self.request.user.is_staff: | 72 | if self.request.user.is_staff: |
73 | categories = Category.objects.all().order_by('name') | 73 | categories = Category.objects.all().order_by('name') |
74 | else: | 74 | else: |
@@ -94,6 +94,39 @@ class IndexView(LoginRequiredMixin, ListView): | @@ -94,6 +94,39 @@ class IndexView(LoginRequiredMixin, ListView): | ||
94 | 94 | ||
95 | return categories | 95 | return categories |
96 | 96 | ||
97 | + def paginate_queryset(self, queryset, page_size): | ||
98 | + paginator = self.get_paginator( | ||
99 | + queryset, page_size, orphans=self.get_paginate_orphans(), | ||
100 | + allow_empty_first_page=self.get_allow_empty()) | ||
101 | + | ||
102 | + page_kwarg = self.page_kwarg | ||
103 | + | ||
104 | + page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1 | ||
105 | + | ||
106 | + if self.kwargs.get('slug'): | ||
107 | + categories = queryset | ||
108 | + | ||
109 | + paginator = Paginator(categories, self.paginate_by) | ||
110 | + | ||
111 | + page = get_category_page(categories, self.kwargs.get('slug'), self.paginate_by) | ||
112 | + | ||
113 | + try: | ||
114 | + page_number = int(page) | ||
115 | + except ValueError: | ||
116 | + if page == 'last': | ||
117 | + page_number = paginator.num_pages | ||
118 | + else: | ||
119 | + raise Http404(_("Page is not 'last', nor can it be converted to an int.")) | ||
120 | + | ||
121 | + try: | ||
122 | + page = paginator.page(page_number) | ||
123 | + return (paginator, page, page.object_list, page.has_other_pages()) | ||
124 | + except InvalidPage as e: | ||
125 | + raise Http404(_('Invalid page (%(page_number)s): %(message)s') % { | ||
126 | + 'page_number': page_number, | ||
127 | + 'message': str(e) | ||
128 | + }) | ||
129 | + | ||
97 | def render_to_response(self, context, **response_kwargs): | 130 | def render_to_response(self, context, **response_kwargs): |
98 | if self.request.user.is_staff: | 131 | if self.request.user.is_staff: |
99 | context['page_template'] = "categories/home_admin_content.html" | 132 | context['page_template'] = "categories/home_admin_content.html" |
@@ -116,11 +149,14 @@ class IndexView(LoginRequiredMixin, ListView): | @@ -116,11 +149,14 @@ class IndexView(LoginRequiredMixin, ListView): | ||
116 | 149 | ||
117 | context['show_buttons'] = True #So it shows subscribe and access buttons | 150 | context['show_buttons'] = True #So it shows subscribe and access buttons |
118 | context['totals'] = self.totals | 151 | context['totals'] = self.totals |
119 | - | 152 | + |
120 | if self.kwargs.get('option'): | 153 | if self.kwargs.get('option'): |
121 | context['all'] = True | 154 | context['all'] = True |
122 | context['title'] = _('All Subjects') | 155 | context['title'] = _('All Subjects') |
123 | 156 | ||
157 | + if self.kwargs.get('slug'): | ||
158 | + context['cat_slug'] = self.kwargs.get('slug') | ||
159 | + | ||
124 | context['subjects_menu_active'] = 'subjects_menu_active' | 160 | context['subjects_menu_active'] = 'subjects_menu_active' |
125 | 161 | ||
126 | return context | 162 | return context |
@@ -0,0 +1,19 @@ | @@ -0,0 +1,19 @@ | ||
1 | +# -*- coding: utf-8 -*- | ||
2 | +# Generated by Django 1.10 on 2017-01-18 20:11 | ||
3 | +from __future__ import unicode_literals | ||
4 | + | ||
5 | +from django.db import migrations | ||
6 | + | ||
7 | + | ||
8 | +class Migration(migrations.Migration): | ||
9 | + | ||
10 | + dependencies = [ | ||
11 | + ('topics', '0003_auto_20170116_2101'), | ||
12 | + ] | ||
13 | + | ||
14 | + operations = [ | ||
15 | + migrations.AlterModelOptions( | ||
16 | + name='topic', | ||
17 | + options={'ordering': ['order'], 'verbose_name': 'Topic', 'verbose_name_plural': 'Topics'}, | ||
18 | + ), | ||
19 | + ] |
topics/templates/topics/create.html
1 | {% extends 'subjects/view.html' %} | 1 | {% extends 'subjects/view.html' %} |
2 | 2 | ||
3 | -{% load django_bootstrap_breadcrumbs %} | 3 | +{% load i18n django_bootstrap_breadcrumbs %} |
4 | 4 | ||
5 | {% block breadcrumbs %} | 5 | {% block breadcrumbs %} |
6 | {{ block.super }} | 6 | {{ block.super }} |
7 | + | ||
7 | {% trans 'Create Topic' as bread %} | 8 | {% trans 'Create Topic' as bread %} |
8 | {% breadcrumb bread 'topics:create' subject.slug %} | 9 | {% breadcrumb bread 'topics:create' subject.slug %} |
9 | {% endblock %} | 10 | {% endblock %} |
topics/templates/topics/update.html
1 | {% extends 'subjects/view.html' %} | 1 | {% extends 'subjects/view.html' %} |
2 | 2 | ||
3 | -{% load django_bootstrap_breadcrumbs %} | 3 | +{% load i18n django_bootstrap_breadcrumbs %} |
4 | 4 | ||
5 | {% block breadcrumbs %} | 5 | {% block breadcrumbs %} |
6 | {{ block.super }} | 6 | {{ block.super }} |
7 | + | ||
7 | {% trans 'Update Topic' as bread %} | 8 | {% trans 'Update Topic' as bread %} |
8 | {% breadcrumb bread 'topics:update' subject.slug topic.slug %} | 9 | {% breadcrumb bread 'topics:update' subject.slug topic.slug %} |
9 | {% endblock %} | 10 | {% endblock %} |
users/templates/users/search.html
@@ -29,7 +29,7 @@ | @@ -29,7 +29,7 @@ | ||
29 | </form> | 29 | </form> |
30 | </div> | 30 | </div> |
31 | <div class="col-md-3"> | 31 | <div class="col-md-3"> |
32 | - <a href="{% url 'users:create' %}" class="pull-right btn btn-primary btn-raised btn-md"><i class="fa fa-plus"></i> {% trans 'Create User' %}</a> | 32 | + <a href="{% url 'users:create' %}" class="pull-right btn btn-success btn-raised btn-md"><i class="fa fa-plus"></i> {% trans 'Create User' %}</a> |
33 | </div> | 33 | </div> |
34 | </div> | 34 | </div> |
35 | 35 | ||
@@ -53,7 +53,7 @@ | @@ -53,7 +53,7 @@ | ||
53 | </div> | 53 | </div> |
54 | <div class="col-md-3"> | 54 | <div class="col-md-3"> |
55 | <div align="right"> | 55 | <div align="right"> |
56 | - <a href="{% url 'users:update' acc.email %}" class="btn btn-primary btn-raised btn-sm"><i class="fa fa-edit"></i> {% trans 'Edit' %}</a> | 56 | + <a href="{% url 'users:update' acc.email %}" class="btn btn-success btn-raised btn-sm"><i class="fa fa-edit"></i> {% trans 'Edit' %}</a> |
57 | <a href="{% url 'users:delete' acc.email %}" class="btn btn-default btn-raised btn-sm"><i class="fa fa-trash"></i> {% trans 'Delete' %}</a> | 57 | <a href="{% url 'users:delete' acc.email %}" class="btn btn-default btn-raised btn-sm"><i class="fa fa-trash"></i> {% trans 'Delete' %}</a> |
58 | </div> | 58 | </div> |
59 | </div> | 59 | </div> |
users/views.py
@@ -72,7 +72,7 @@ class SearchView(braces_mixins.LoginRequiredMixin, braces_mixins.StaffuserRequir | @@ -72,7 +72,7 @@ class SearchView(braces_mixins.LoginRequiredMixin, braces_mixins.StaffuserRequir | ||
72 | def get_queryset(self): | 72 | def get_queryset(self): |
73 | search = self.request.GET.get('search', '') | 73 | search = self.request.GET.get('search', '') |
74 | 74 | ||
75 | - users = User.objects.filter(Q(username__icontains = search) | Q(last_name__icontains = search) | Q(social_name__icontains = search) | Q(email__icontains = search)).order_by('social_name','username').exclude(email = self.request.user.email) | 75 | + users = User.objects.filter(Q(username__icontains = search) | Q(last_name__icontains = search) | Q(social_name__icontains = search) | Q(email__icontains = search)).distinct().order_by('social_name','username').exclude(email = self.request.user.email) |
76 | 76 | ||
77 | return users | 77 | return users |
78 | 78 |