Commit bf46f0ba2f823ef6bac301ae7bbc4caa3be12b34
Exists in
master
and in
3 other branches
Merge branch 'refactoring' of https://github.com/amadeusproject/amadeuslms into refactoring
Showing
10 changed files
with
337 additions
and
9 deletions
Show diff stats
amadeus/static/css/base/amadeus.css
| @@ -968,4 +968,13 @@ li.item .notify_badge { | @@ -968,4 +968,13 @@ li.item .notify_badge { | ||
| 968 | .mural .post_make .post-field h4 { | 968 | .mural .post_make .post-field h4 { |
| 969 | cursor: text; | 969 | cursor: text; |
| 970 | font-style: italic; | 970 | font-style: italic; |
| 971 | +} | ||
| 972 | + | ||
| 973 | +.post_action i { | ||
| 974 | + font-size: 45px; | ||
| 975 | +} | ||
| 976 | + | ||
| 977 | +.post-button { | ||
| 978 | + padding-right: inherit !important; | ||
| 979 | + padding-left: inherit !important; | ||
| 971 | } | 980 | } |
| 972 | \ No newline at end of file | 981 | \ No newline at end of file |
amadeus/static/css/themes/green.css
| @@ -504,6 +504,10 @@ a.add-row { | @@ -504,6 +504,10 @@ a.add-row { | ||
| 504 | color: #CCCCCC; | 504 | color: #CCCCCC; |
| 505 | } | 505 | } |
| 506 | 506 | ||
| 507 | +.post_action i { | ||
| 508 | + color: #1d8fe0; | ||
| 509 | +} | ||
| 510 | + | ||
| 507 | @media(max-width: 768px) { | 511 | @media(max-width: 768px) { |
| 508 | .navbar .navbar-nav .dropdown .dropdown-menu li > a { | 512 | .navbar .navbar-nav .dropdown .dropdown-menu li > a { |
| 509 | color: #333333 !important; | 513 | color: #333333 !important; |
mural/forms.py
| @@ -0,0 +1,41 @@ | @@ -0,0 +1,41 @@ | ||
| 1 | +# coding=utf-8 | ||
| 2 | +from django import forms | ||
| 3 | +from django.utils.translation import ugettext_lazy as _ | ||
| 4 | +from django.utils.html import strip_tags | ||
| 5 | + | ||
| 6 | +from .models import GeneralPost | ||
| 7 | + | ||
| 8 | +class Validation(forms.ModelForm): | ||
| 9 | + MAX_UPLOAD_SIZE = 5*1024*1024 | ||
| 10 | + | ||
| 11 | + def clean_post(self): | ||
| 12 | + post = self.cleaned_data.get('post', '') | ||
| 13 | + cleaned_post = strip_tags(post) | ||
| 14 | + | ||
| 15 | + if cleaned_post == '': | ||
| 16 | + self._errors['post'] = [_('This field is required.')] | ||
| 17 | + | ||
| 18 | + return ValueError | ||
| 19 | + | ||
| 20 | + return post | ||
| 21 | + | ||
| 22 | + def clean_image(self): | ||
| 23 | + image = self.cleaned_data.get('image', False) | ||
| 24 | + | ||
| 25 | + if image: | ||
| 26 | + if hasattr(image, '_size'): | ||
| 27 | + if image._size > self.MAX_UPLOAD_SIZE: | ||
| 28 | + self._errors['image'] = [_("The image is too large. It should have less than 5MB.")] | ||
| 29 | + | ||
| 30 | + return ValueError | ||
| 31 | + | ||
| 32 | + return image | ||
| 33 | + | ||
| 34 | +class GeneralPostForm(Validation): | ||
| 35 | + class Meta: | ||
| 36 | + model = GeneralPost | ||
| 37 | + fields = ['action', 'post', 'image'] | ||
| 38 | + widgets = { | ||
| 39 | + 'action': forms.RadioSelect, | ||
| 40 | + 'post': forms.Textarea | ||
| 41 | + } | ||
| 0 | \ No newline at end of file | 42 | \ No newline at end of file |
| @@ -0,0 +1,20 @@ | @@ -0,0 +1,20 @@ | ||
| 1 | +# -*- coding: utf-8 -*- | ||
| 2 | +# Generated by Django 1.10 on 2017-02-04 19:25 | ||
| 3 | +from __future__ import unicode_literals | ||
| 4 | + | ||
| 5 | +from django.db import migrations, models | ||
| 6 | + | ||
| 7 | + | ||
| 8 | +class Migration(migrations.Migration): | ||
| 9 | + | ||
| 10 | + dependencies = [ | ||
| 11 | + ('mural', '0002_muralvisualizations'), | ||
| 12 | + ] | ||
| 13 | + | ||
| 14 | + operations = [ | ||
| 15 | + migrations.AlterField( | ||
| 16 | + model_name='mural', | ||
| 17 | + name='action', | ||
| 18 | + field=models.CharField(choices=[('comment', 'Comment'), ('help', 'Ask for Help')], max_length=100, verbose_name='Action'), | ||
| 19 | + ), | ||
| 20 | + ] |
mural/models.py
| @@ -16,7 +16,7 @@ def validate_img_extension(value): | @@ -16,7 +16,7 @@ def validate_img_extension(value): | ||
| 16 | raise ValidationError(_('File not supported.')) | 16 | raise ValidationError(_('File not supported.')) |
| 17 | 17 | ||
| 18 | class Mural(KnowsChild): | 18 | class Mural(KnowsChild): |
| 19 | - action = models.CharField(_('Action'), max_length = 100, choices = (("comment", _("Comment")), ("help", _("Ask for Help"))), blank = True) | 19 | + action = models.CharField(_('Action'), max_length = 100, default = "comment", choices = (("comment", _("Comment")), ("help", _("Ask for Help")))) |
| 20 | post = models.TextField(_('Post'), blank = True) | 20 | post = models.TextField(_('Post'), blank = True) |
| 21 | image = models.ImageField(verbose_name = _('Image'), null=True, blank = True, upload_to = 'posts/', validators = [validate_img_extension]) | 21 | image = models.ImageField(verbose_name = _('Image'), null=True, blank = True, upload_to = 'posts/', validators = [validate_img_extension]) |
| 22 | user = models.ForeignKey(User, verbose_name = _('User'), related_name = "post_user", null = True) | 22 | user = models.ForeignKey(User, verbose_name = _('User'), related_name = "post_user", null = True) |
| @@ -0,0 +1,142 @@ | @@ -0,0 +1,142 @@ | ||
| 1 | +{% load static i18n %} | ||
| 2 | +{% load widget_tweaks %} | ||
| 3 | + | ||
| 4 | +<div class="modal-dialog" role="document"> | ||
| 5 | + <div class="modal-content"> | ||
| 6 | + <div class="modal-body"> | ||
| 7 | + <form id="post-form" method="post" action="{{ form_url }}" enctype="multipart/form-data"> | ||
| 8 | + {% csrf_token %} | ||
| 9 | + <div class="row"> | ||
| 10 | + <div class="form-group{% if form.has_error %} has-error {% endif %} text-center col-md-6"> | ||
| 11 | + <label for="{{ form.action.0.id_for_label }}" class="text-center post_action"> | ||
| 12 | + <span class="radio"> | ||
| 13 | + <i class="fa fa-commenting-o"></i> | ||
| 14 | + <br clear="all" /> | ||
| 15 | + {{ form.action.0 }} | ||
| 16 | + </span> | ||
| 17 | + </label> | ||
| 18 | + </div> | ||
| 19 | + <div class="form-group{% if form.has_error %} has-error {% endif %} text-center col-md-6"> | ||
| 20 | + <label for="{{ form.action.1.id_for_label }}" class="text-center post_action"> | ||
| 21 | + <span class="radio"> | ||
| 22 | + <i class="fa fa-comments-o"></i> | ||
| 23 | + <br clear="all" /> | ||
| 24 | + {{ form.action.1 }} | ||
| 25 | + </span> | ||
| 26 | + </label> | ||
| 27 | + </div> | ||
| 28 | + </div> | ||
| 29 | + | ||
| 30 | + <div class="form-group{% if form.has_error %} has-error {% endif %}"> | ||
| 31 | + <label for="{{ form.post.auto_id }}">{{ form.post.label }} <span>*</span></label> | ||
| 32 | + {% render_field form.post class='form-control text_simple_wysiwyg' %} | ||
| 33 | + | ||
| 34 | + <span id="helpBlock" class="help-block">{{ form.post.help_text }}</span> | ||
| 35 | + | ||
| 36 | + {% if form.post.errors %} | ||
| 37 | + <div class="alert alert-danger alert-dismissible" role="alert"> | ||
| 38 | + <button type="button" class="close" data-dismiss="alert" aria-label="Close"> | ||
| 39 | + <span aria-hidden="true">×</span> | ||
| 40 | + </button> | ||
| 41 | + <ul> | ||
| 42 | + {% for error in form.post.errors %} | ||
| 43 | + <li>{{ error }}</li> | ||
| 44 | + {% endfor %} | ||
| 45 | + </ul> | ||
| 46 | + </div> | ||
| 47 | + {% endif %} | ||
| 48 | + </div> | ||
| 49 | + | ||
| 50 | + <div class="form-group{% if form.has_error %} has-error {% endif %} is-fileinput"> | ||
| 51 | + {% render_field form.image %} | ||
| 52 | + | ||
| 53 | + <div class="input-group common-file-input"> | ||
| 54 | + <input type="text" readonly="" class="form-control" placeholder="{% trans 'Choose your photo...' %}"> | ||
| 55 | + <span class="input-group-btn input-group-sm"> | ||
| 56 | + <button type="button" class="btn btn-fab btn-fab-mini"> | ||
| 57 | + <i class="material-icons">attach_file</i> | ||
| 58 | + </button> | ||
| 59 | + </span> | ||
| 60 | + </div> | ||
| 61 | + | ||
| 62 | + <div class="filedrag"> | ||
| 63 | + {% trans 'Click or drop the file here' %}<br /> | ||
| 64 | + | ||
| 65 | + <small>{% trans 'The file could not exceed 5MB.' %}</small> | ||
| 66 | + </div> | ||
| 67 | + | ||
| 68 | + <span id="helpBlock" class="help-block">{{ form.image.help_text }}</span> | ||
| 69 | + | ||
| 70 | + {% if form.image.errors %} | ||
| 71 | + <div class="alert alert-danger alert-dismissible" role="alert"> | ||
| 72 | + <button type="button" class="close" data-dismiss="alert" aria-label="Close"> | ||
| 73 | + <span aria-hidden="true">×</span> | ||
| 74 | + </button> | ||
| 75 | + <ul> | ||
| 76 | + {% for error in form.image.errors %} | ||
| 77 | + <li>{{ error }}</li> | ||
| 78 | + {% endfor %} | ||
| 79 | + </ul> | ||
| 80 | + </div> | ||
| 81 | + {% endif %} | ||
| 82 | + </div> | ||
| 83 | + </form> | ||
| 84 | + </div> | ||
| 85 | + <div class="modal-footer"> | ||
| 86 | + <div class="col-md-12"> | ||
| 87 | + <button type="submit" id="button" form="post-form" class="btn btn-success btn-raised post-button pull-left">{% trans "Save" %}</button> | ||
| 88 | + <button type="button" class="btn btn-raised btn-default pull-right" data-dismiss="modal">{% trans "Cancel" %}</button> | ||
| 89 | + </div> | ||
| 90 | + </div> | ||
| 91 | + </div> | ||
| 92 | +</div> | ||
| 93 | + | ||
| 94 | +<script type="text/javascript"> | ||
| 95 | + $(function () { | ||
| 96 | + $('.text_simple_wysiwyg').summernote({ | ||
| 97 | + dialogsInBody: true, | ||
| 98 | + height: 150, | ||
| 99 | + toolbar: [ | ||
| 100 | + // [groupName, [list of button]] | ||
| 101 | + ['style', ['bold', 'italic']], | ||
| 102 | + ['insert', ['link']] | ||
| 103 | + ] | ||
| 104 | + }); | ||
| 105 | + | ||
| 106 | + $.material.init(); | ||
| 107 | + | ||
| 108 | + if (window.File && window.FileList && window.FileReader) { | ||
| 109 | + Init(); | ||
| 110 | + } | ||
| 111 | + }); | ||
| 112 | + | ||
| 113 | + // initialize | ||
| 114 | + function Init() { | ||
| 115 | + var small = $("#id_image"), | ||
| 116 | + filedrag = $(".filedrag"), | ||
| 117 | + common = $(".common-file-input"); | ||
| 118 | + | ||
| 119 | + // file select | ||
| 120 | + small.on("change", FileSelectHandler); | ||
| 121 | + | ||
| 122 | + // is XHR2 available? | ||
| 123 | + var xhr = new XMLHttpRequest(); | ||
| 124 | + if (xhr.upload) { | ||
| 125 | + // file drop | ||
| 126 | + filedrag.on("drop", FileSelectHandler); | ||
| 127 | + filedrag.attr('style', 'display:block'); | ||
| 128 | + common.attr('style', 'display:none'); | ||
| 129 | + } | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + // file selection | ||
| 133 | + function FileSelectHandler(e) { | ||
| 134 | + var files = e.target.files || e.dataTransfer.files, | ||
| 135 | + parent = $(e.target.offsetParent); | ||
| 136 | + | ||
| 137 | + // process all File objects | ||
| 138 | + for (var i = 0, f; f = files[i]; i++) { | ||
| 139 | + parent.find('.filedrag').html(f.name); | ||
| 140 | + } | ||
| 141 | + } | ||
| 142 | +</script> |
mural/templates/mural/list.html
| @@ -29,21 +29,72 @@ | @@ -29,21 +29,72 @@ | ||
| 29 | </div> | 29 | </div> |
| 30 | <div class="col-lg-11 col-md-11 col-sm-11 col-xs-11 post-field"> | 30 | <div class="col-lg-11 col-md-11 col-sm-11 col-xs-11 post-field"> |
| 31 | <div> | 31 | <div> |
| 32 | - <h4>{% trans 'Wish to make a new post?' %}</h4> | 32 | + <h4 data-url="{% url 'mural:create_general' %}">{% trans 'Wish to make a new post?' %}</h4> |
| 33 | </div> | 33 | </div> |
| 34 | </div> | 34 | </div> |
| 35 | </div> | 35 | </div> |
| 36 | </div> | 36 | </div> |
| 37 | 37 | ||
| 38 | - {% if posts.count > 0 %} | ||
| 39 | - {% else %} | ||
| 40 | - <div class="text-center no-subjects"> | ||
| 41 | - <i class="fa fa-list"></i> | ||
| 42 | - <h4>{% trans 'There are no posts in this mural yet.' %}</h4> | ||
| 43 | - </div> | ||
| 44 | - {% endif %} | 38 | + <div class="posts"> |
| 39 | + {% for post in posts %} | ||
| 40 | + {% include 'mural/_view.html' %} | ||
| 41 | + {% endfor %} | ||
| 42 | + </div> | ||
| 43 | + <div class="text-center no-subjects" {% if posts.count > 0 %} style="display:none" {% endif %}> | ||
| 44 | + <i class="fa fa-list"></i> | ||
| 45 | + <h4>{% trans 'There are no posts in this mural yet.' %}</h4> | ||
| 46 | + </div> | ||
| 45 | </div> | 47 | </div> |
| 46 | <div class="col-md-3 col-sm-3 col-xs-3"> | 48 | <div class="col-md-3 col-sm-3 col-xs-3"> |
| 47 | </div> | 49 | </div> |
| 48 | </div> | 50 | </div> |
| 51 | + | ||
| 52 | + <div class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"></div> | ||
| 53 | + | ||
| 54 | + <script type="text/javascript"> | ||
| 55 | + $(function () { | ||
| 56 | + $(".post-field").click(function () { | ||
| 57 | + var url = $(this).find('h4').data('url'); | ||
| 58 | + | ||
| 59 | + $.ajax({ | ||
| 60 | + url: url, | ||
| 61 | + success: function (data) { | ||
| 62 | + $('.modal').html(data); | ||
| 63 | + | ||
| 64 | + setPostFormSubmit(); | ||
| 65 | + | ||
| 66 | + $('.modal').modal('show'); | ||
| 67 | + } | ||
| 68 | + }) | ||
| 69 | + }); | ||
| 70 | + }); | ||
| 71 | + | ||
| 72 | + function setPostFormSubmit() { | ||
| 73 | + var frm = $('#post-form'); | ||
| 74 | + | ||
| 75 | + frm.submit(function () { | ||
| 76 | + $.ajax({ | ||
| 77 | + type: frm.attr('method'), | ||
| 78 | + url: frm.attr('action'), | ||
| 79 | + data: frm.serialize(), | ||
| 80 | + dataType: "json", | ||
| 81 | + success: function (data) { | ||
| 82 | + $('.posts').prepend(data.view); | ||
| 83 | + | ||
| 84 | + $('.no-subjects').attr('style', 'display:none'); | ||
| 85 | + | ||
| 86 | + $('.modal').modal('hide'); | ||
| 87 | + | ||
| 88 | + alertify.success(data.message); | ||
| 89 | + }, | ||
| 90 | + error: function(data) { | ||
| 91 | + $(".modal").html(data.responseText); | ||
| 92 | + setPostFormSubmit(); | ||
| 93 | + } | ||
| 94 | + }); | ||
| 95 | + | ||
| 96 | + return false; | ||
| 97 | + }); | ||
| 98 | + } | ||
| 99 | + </script> | ||
| 49 | {% endblock %} | 100 | {% endblock %} |
| 50 | \ No newline at end of file | 101 | \ No newline at end of file |
mural/urls.py
| @@ -3,4 +3,6 @@ from . import views | @@ -3,4 +3,6 @@ from . import views | ||
| 3 | 3 | ||
| 4 | urlpatterns = [ | 4 | urlpatterns = [ |
| 5 | url(r'^$', views.GeneralIndex.as_view(), name='manage_general'), | 5 | url(r'^$', views.GeneralIndex.as_view(), name='manage_general'), |
| 6 | + url(r'^create_gen/$', views.GeneralCreate.as_view(), name='create_general'), | ||
| 7 | + url(r'^render_post/([\w_-]+)/$', views.render_gen_post, name='render_post_general'), | ||
| 6 | ] | 8 | ] |
| 7 | \ No newline at end of file | 9 | \ No newline at end of file |
mural/views.py
| @@ -2,12 +2,16 @@ from django.shortcuts import get_object_or_404, redirect, render | @@ -2,12 +2,16 @@ from django.shortcuts import get_object_or_404, redirect, render | ||
| 2 | from django.views import generic | 2 | from django.views import generic |
| 3 | from django.contrib import messages | 3 | from django.contrib import messages |
| 4 | from django.http import JsonResponse | 4 | from django.http import JsonResponse |
| 5 | +from django.template.loader import render_to_string | ||
| 5 | from django.core.urlresolvers import reverse, reverse_lazy | 6 | from django.core.urlresolvers import reverse, reverse_lazy |
| 6 | from django.utils.translation import ugettext_lazy as _ | 7 | from django.utils.translation import ugettext_lazy as _ |
| 7 | from django.contrib.auth.mixins import LoginRequiredMixin | 8 | from django.contrib.auth.mixins import LoginRequiredMixin |
| 8 | from django.db.models import Q, Count | 9 | from django.db.models import Q, Count |
| 9 | 10 | ||
| 11 | +from users.models import User | ||
| 12 | + | ||
| 10 | from .models import GeneralPost, CategoryPost, SubjectPost, MuralVisualizations | 13 | from .models import GeneralPost, CategoryPost, SubjectPost, MuralVisualizations |
| 14 | +from .forms import GeneralPostForm | ||
| 11 | 15 | ||
| 12 | class GeneralIndex(LoginRequiredMixin, generic.ListView): | 16 | class GeneralIndex(LoginRequiredMixin, generic.ListView): |
| 13 | login_url = reverse_lazy("users:login") | 17 | login_url = reverse_lazy("users:login") |
| @@ -39,3 +43,53 @@ class GeneralIndex(LoginRequiredMixin, generic.ListView): | @@ -39,3 +43,53 @@ class GeneralIndex(LoginRequiredMixin, generic.ListView): | ||
| 39 | context['mural_menu_active'] = 'subjects_menu_active' | 43 | context['mural_menu_active'] = 'subjects_menu_active' |
| 40 | 44 | ||
| 41 | return context | 45 | return context |
| 46 | + | ||
| 47 | +class GeneralCreate(LoginRequiredMixin, generic.edit.CreateView): | ||
| 48 | + login_url = reverse_lazy("users:login") | ||
| 49 | + redirect_field_name = 'next' | ||
| 50 | + | ||
| 51 | + template_name = 'mural/_form.html' | ||
| 52 | + form_class = GeneralPostForm | ||
| 53 | + | ||
| 54 | + def form_invalid(self, form): | ||
| 55 | + context = super(GeneralCreate, self).form_invalid(form) | ||
| 56 | + context.status_code = 400 | ||
| 57 | + | ||
| 58 | + return context | ||
| 59 | + | ||
| 60 | + def form_valid(self, form): | ||
| 61 | + self.object = form.save(commit = False) | ||
| 62 | + | ||
| 63 | + self.object.user = self.request.user | ||
| 64 | + | ||
| 65 | + self.object.save() | ||
| 66 | + | ||
| 67 | + users = User.objects.all().exclude(id = self.request.user.id) | ||
| 68 | + entries = [] | ||
| 69 | + | ||
| 70 | + for user in users: | ||
| 71 | + entries.append(MuralVisualizations(viewed = False, user = user, post = self.object)) | ||
| 72 | + | ||
| 73 | + MuralVisualizations.objects.bulk_create(entries) | ||
| 74 | + | ||
| 75 | + return super(GeneralCreate, self).form_valid(form) | ||
| 76 | + | ||
| 77 | + def get_context_data(self, *args, **kwargs): | ||
| 78 | + context = super(GeneralCreate, self).get_context_data(*args, **kwargs) | ||
| 79 | + | ||
| 80 | + context['form_url'] = reverse_lazy("mural:create_general") | ||
| 81 | + | ||
| 82 | + return context | ||
| 83 | + | ||
| 84 | + def get_success_url(self): | ||
| 85 | + return reverse_lazy('mural:render_post_general', args = (self.object.id, )) | ||
| 86 | + | ||
| 87 | +def render_gen_post(request, post): | ||
| 88 | + post = get_object_or_404(GeneralPost, id = post) | ||
| 89 | + | ||
| 90 | + context = {} | ||
| 91 | + context['post'] = post | ||
| 92 | + | ||
| 93 | + html = render_to_string("mural/_view.html", context, request) | ||
| 94 | + | ||
| 95 | + return JsonResponse({'message': _('Your post was published successfully!'), 'view': html}) |