diff --git a/SciPost_v1/settings/base.py b/SciPost_v1/settings/base.py index ec132d3dd4453c2710cf181d9fbda302e98005c3..6bbf98dffecc06f1664e6a5ee1cc0fcfc0ccc8be 100644 --- a/SciPost_v1/settings/base.py +++ b/SciPost_v1/settings/base.py @@ -86,9 +86,11 @@ INSTALLED_APPS = ( 'commentaries', 'comments', 'journals', + 'news', 'scipost', 'submissions', 'theses', + 'virtualmeetings', 'webpack_loader' ) diff --git a/SciPost_v1/urls.py b/SciPost_v1/urls.py index 9b85d34e8e3967fdfc82a646d9d8c76443eb3c9a..9391da29315f2d285bc84dc132c7e7dafa617cb8 100644 --- a/SciPost_v1/urls.py +++ b/SciPost_v1/urls.py @@ -31,5 +31,5 @@ urlpatterns = [ url(r'^submission/', include('submissions.urls', namespace="submissions")), url(r'^theses/', include('theses.urls', namespace="theses")), url(r'^thesis/', include('theses.urls', namespace="theses")), - # url(r'^captcha/', include('captcha.urls')), + url(r'^news/', include('news.urls', namespace="news")), ] diff --git a/news/__init__.py b/news/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/news/admin.py b/news/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..43253c6cc5b48d7b606f34ea7e5f57f639625ee2 --- /dev/null +++ b/news/admin.py @@ -0,0 +1,10 @@ +from django.contrib import admin + +from .models import NewsItem + + +class NewsItemAdmin(admin.ModelAdmin): + search_fields = ['blurb', 'followup_link_text'] + + +admin.site.register(NewsItem, NewsItemAdmin) diff --git a/news/apps.py b/news/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..5a7b92d0f844e1bd89c73e7bba369b07298ae70a --- /dev/null +++ b/news/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class NewsConfig(AppConfig): + name = 'news' diff --git a/news/migrations/0001_initial.py b/news/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..4df07209043385758fe45a5f4a24096fbfd20415 --- /dev/null +++ b/news/migrations/0001_initial.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-03-06 07:04 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('scipost', '0039_auto_20170306_0804'), + ] + + state_operations = [ + migrations.CreateModel( + name='NewsItem', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateField()), + ('headline', models.CharField(max_length=300)), + ('blurb', models.TextField()), + ('followup_link', models.URLField(blank=True, null=True)), + ('followup_link_text', models.CharField(blank=True, max_length=300, null=True)), + ], + options={ + 'db_table': 'scipost_newsitem', + }, + ), + ] + + operations = [ + migrations.SeparateDatabaseAndState(state_operations=state_operations) + ] diff --git a/news/migrations/__init__.py b/news/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/news/models.py b/news/models.py new file mode 100644 index 0000000000000000000000000000000000000000..d421b8c4bbae2b55c595dd0d4fcec7347509dc6f --- /dev/null +++ b/news/models.py @@ -0,0 +1,52 @@ +from django.db import models +from django.template import Template, Context + + +class NewsItem(models.Model): + date = models.DateField() + headline = models.CharField(max_length=300) + blurb = models.TextField() + followup_link = models.URLField(blank=True, null=True) + followup_link_text = models.CharField(max_length=300, blank=True, null=True) + + class Meta: + db_table = 'scipost_newsitem' + + def __str__(self): + return self.date.strftime('%Y-%m-%d') + ', ' + self.headline + + def descriptor_full(self): + """ For News page. """ + descriptor = ('<div class="flex-greybox640">' + '<h3 class="NewsHeadline">{{ headline }}</h3>' + '<p>{{ date }}</p>' + '<p>{{ blurb }}</p>' + ) + context = Context({'headline': self.headline, + 'date': self.date.strftime('%Y-%m-%d'), + 'blurb': self.blurb, }) + if self.followup_link: + descriptor += '<p><a href="{{ followup_link }}">{{ followup_link_text }}</a></p>' + context['followup_link'] = self.followup_link + context['followup_link_text'] = self.followup_link_text + descriptor += '</div>' + template = Template(descriptor) + return template.render(context) + + def descriptor_small(self): + """ For index page. """ + descriptor = ('<h3 class="NewsHeadline">{{ headline }}</h3>' + '<div class="p-2">' + '<p>{{ date }}</p>' + '<p>{{ blurb }}</p>' + ) + context = Context({'headline': self.headline, + 'date': self.date.strftime('%Y-%m-%d'), + 'blurb': self.blurb, }) + if self.followup_link: + descriptor += '<p><a href="{{ followup_link }}">{{ followup_link_text }}</a></p>' + context['followup_link'] = self.followup_link + context['followup_link_text'] = self.followup_link_text + descriptor += '</div>' + template = Template(descriptor) + return template.render(context) diff --git a/news/tests.py b/news/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6 --- /dev/null +++ b/news/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/news/urls.py b/news/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..a84f2906bb39ef2937fe1a6ca284e8e6a77368e7 --- /dev/null +++ b/news/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls import url + +from . import views + +urlpatterns = [ + url(r'^$', views.news, name='news'), +] diff --git a/news/views.py b/news/views.py new file mode 100644 index 0000000000000000000000000000000000000000..6e6773d73118132b4eeeb2ca1c21b4aa6b1dd138 --- /dev/null +++ b/news/views.py @@ -0,0 +1,9 @@ +from django.shortcuts import render + +from .models import NewsItem + + +def news(request): + newsitems = NewsItem.objects.all().order_by('-date') + context = {'newsitems': newsitems} + return render(request, 'scipost/news.html', context) diff --git a/scipost/admin.py b/scipost/admin.py index 799517e9b63c9541937e8669a9945ea2361b77f9..ccb5be7f2600f04a7c149736635d12225bef97aa 100644 --- a/scipost/admin.py +++ b/scipost/admin.py @@ -5,60 +5,74 @@ from django.contrib.auth.models import User, Permission from guardian.admin import GuardedModelAdmin -from scipost.models import * +from scipost.models import Contributor, Remark, List,\ + DraftInvitation, Node, Arc, Graph, Team, AffiliationObject,\ + SupportingPartner, SPBMembershipAgreement, RegistrationInvitation,\ + AuthorshipClaim, PrecookedEmail +from virtualmeetings.models import VGM, Feedback, Nomination, Motion + class ContributorInline(admin.StackedInline): model = Contributor + class UserAdmin(UserAdmin): inlines = [ ContributorInline, ] search_fields = ['last_name', 'email'] + admin.site.unregister(User) admin.site.register(User, UserAdmin) -class VGMAdmin(admin.ModelAdmin): - search_fields = ['start_date'] - -admin.site.register(VGM, VGMAdmin) - - -class FeedbackAdmin(admin.ModelAdmin): - search_fields = ['feedback', 'by'] - -admin.site.register(Feedback, FeedbackAdmin) - - -class NominationAdmin(admin.ModelAdmin): - search_fields = ['last_name', 'first_name', 'by'] - -admin.site.register(Nomination, NominationAdmin) - - -class MotionAdmin(admin.ModelAdmin): - search_fields = ['background', 'motion', 'put_forward_by'] - -admin.site.register(Motion, MotionAdmin) +# class VGMAdmin(admin.ModelAdmin): +# search_fields = ['start_date'] +# +# +# admin.site.register(VGM, VGMAdmin) +# +# +# class FeedbackAdmin(admin.ModelAdmin): +# search_fields = ['feedback', 'by'] +# +# +# admin.site.register(Feedback, FeedbackAdmin) +# +# +# class NominationAdmin(admin.ModelAdmin): +# search_fields = ['last_name', 'first_name', 'by'] +# +# +# admin.site.register(Nomination, NominationAdmin) +# +# +# class MotionAdmin(admin.ModelAdmin): +# search_fields = ['background', 'motion', 'put_forward_by'] +# +# +# admin.site.register(Motion, MotionAdmin) class RemarkAdmin(admin.ModelAdmin): search_fields = ['contributor', 'remark'] + admin.site.register(Remark, RemarkAdmin) class DraftInvitationAdmin(admin.ModelAdmin): search_fields = ['first_name', 'last_name', 'email', 'processed'] + admin.site.register(DraftInvitation, DraftInvitationAdmin) class RegistrationInvitationAdmin(admin.ModelAdmin): search_fields = ['first_name', 'last_name', 'email', 'invitation_key'] + admin.site.register(RegistrationInvitation, RegistrationInvitationAdmin) @@ -69,34 +83,26 @@ admin.site.register(Permission) class PrecookedEmailAdmin(admin.ModelAdmin): search_fields = ['email_subject', 'email_text', 'emailed_to'] -admin.site.register(PrecookedEmail, PrecookedEmailAdmin) - - -class NewsItemAdmin(admin.ModelAdmin): - search_fields = ['blurb', 'followup_link_text'] -admin.site.register(NewsItem, NewsItemAdmin) +admin.site.register(PrecookedEmail, PrecookedEmailAdmin) class ListAdmin(GuardedModelAdmin): search_fields = ['owner', 'title'] -admin.site.register(List, ListAdmin) +admin.site.register(List, ListAdmin) admin.site.register(Team) -#admin.site.register(Graph) - -#admin.site.register(Node) - -#admin.site.register(Arc) class NodeInline(admin.StackedInline): model = Node + class ArcInline(admin.StackedInline): model = Arc + class GraphAdmin(GuardedModelAdmin): inlines = [ NodeInline, @@ -104,18 +110,21 @@ class GraphAdmin(GuardedModelAdmin): ] search_fields = ['owner___user__last_name', 'title'] + admin.site.register(Graph, GraphAdmin) class AffiliationObjectAdmin(admin.ModelAdmin): search_fields = ['country', 'institution', 'subunit'] + admin.site.register(AffiliationObject, AffiliationObjectAdmin) class SPBMembershipAgreementInline(admin.StackedInline): model = SPBMembershipAgreement + class SupportingPartnerAdmin(admin.ModelAdmin): search_fields = ['institution', 'institution_acronym', 'institution_address', 'contact_person'] @@ -123,4 +132,5 @@ class SupportingPartnerAdmin(admin.ModelAdmin): SPBMembershipAgreementInline, ] + admin.site.register(SupportingPartner, SupportingPartnerAdmin) diff --git a/scipost/feeds.py b/scipost/feeds.py index 297ea1c779d387687b71a39c3a02abe3f7809c70..29ea0a31d6fced705e0c8fd6ac77381e7ed818f8 100644 --- a/scipost/feeds.py +++ b/scipost/feeds.py @@ -5,10 +5,10 @@ from django.utils.feedgenerator import Atom1Feed from django.core.urlresolvers import reverse from django.db.models import Q -from scipost.models import NewsItem from scipost.models import subject_areas_dict from comments.models import Comment from journals.models import Publication +from news.models import NewsItem from submissions.models import Submission, SUBMISSION_STATUS_PUBLICLY_INVISIBLE diff --git a/scipost/forms.py b/scipost/forms.py index 7c628d2f224eff4db20149f95e7765ffc3137e63..04f36192dbf87b171561b533c64707c7afffa624 100644 --- a/scipost/forms.py +++ b/scipost/forms.py @@ -16,8 +16,8 @@ from .models import TITLE_CHOICES, SCIPOST_FROM_ADDRESSES, ARC_LENGTHS,\ Contributor, DraftInvitation, RegistrationInvitation,\ SupportingPartner, SPBMembershipAgreement,\ UnavailabilityPeriod, PrecookedEmail,\ - List, Team, Graph, Node,\ - Feedback, Nomination, Motion + List, Team, Graph, Node +from virtualmeetings.models import Feedback, Nomination, Motion from journals.models import Publication from submissions.models import SUBMISSION_STATUS_PUBLICLY_UNLISTED diff --git a/scipost/migrations/0039_auto_20170306_0804.py b/scipost/migrations/0039_auto_20170306_0804.py new file mode 100644 index 0000000000000000000000000000000000000000..49894a205825fdbe083d86f688b835138d7516ce --- /dev/null +++ b/scipost/migrations/0039_auto_20170306_0804.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-03-06 07:04 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('scipost', '0038_nomination_webpage'), + ] + + state_operations = [ + migrations.RemoveField( + model_name='feedback', + name='VGM', + ), + migrations.RemoveField( + model_name='feedback', + name='by', + ), + migrations.RemoveField( + model_name='motion', + name='VGM', + ), + migrations.RemoveField( + model_name='motion', + name='in_agreement', + ), + migrations.RemoveField( + model_name='motion', + name='in_disagreement', + ), + migrations.RemoveField( + model_name='motion', + name='in_notsure', + ), + migrations.RemoveField( + model_name='motion', + name='put_forward_by', + ), + migrations.DeleteModel( + name='NewsItem', + ), + migrations.RemoveField( + model_name='nomination', + name='VGM', + ), + migrations.RemoveField( + model_name='nomination', + name='by', + ), + migrations.RemoveField( + model_name='nomination', + name='in_agreement', + ), + migrations.RemoveField( + model_name='nomination', + name='in_disagreement', + ), + migrations.RemoveField( + model_name='nomination', + name='in_notsure', + ), + migrations.AlterField( + model_name='remark', + name='feedback', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='virtualmeetings.Feedback'), + ), + migrations.AlterField( + model_name='remark', + name='motion', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='virtualmeetings.Motion'), + ), + migrations.AlterField( + model_name='remark', + name='nomination', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='virtualmeetings.Nomination'), + ), + migrations.DeleteModel( + name='Feedback', + ), + migrations.DeleteModel( + name='Motion', + ), + migrations.DeleteModel( + name='Nomination', + ), + migrations.DeleteModel( + name='VGM', + ), + ] + + operations = [ + migrations.SeparateDatabaseAndState(state_operations=state_operations) + ] diff --git a/scipost/models.py b/scipost/models.py index a29a48f14a27c5181be06a759e02af470a485aba..4bde7f7afa12b3095ad0c2aff68273a00ba6db5a 100644 --- a/scipost/models.py +++ b/scipost/models.py @@ -251,11 +251,11 @@ class UnavailabilityPeriod(models.Model): class Remark(models.Model): contributor = models.ForeignKey(Contributor, on_delete=models.CASCADE) - feedback = models.ForeignKey('scipost.Feedback', on_delete=models.CASCADE, + feedback = models.ForeignKey('virtualmeetings.Feedback', on_delete=models.CASCADE, blank=True, null=True) - nomination = models.ForeignKey('scipost.Nomination', on_delete=models.CASCADE, + nomination = models.ForeignKey('virtualmeetings.Nomination', on_delete=models.CASCADE, blank=True, null=True) - motion = models.ForeignKey('scipost.Motion', on_delete=models.CASCADE, + motion = models.ForeignKey('virtualmeetings.Motion', on_delete=models.CASCADE, blank=True, null=True) submission = models.ForeignKey('submissions.Submission', on_delete=models.CASCADE, @@ -428,269 +428,218 @@ class PrecookedEmail(models.Model): return self.email_subject -############# -# NewsItems # -############# - -class NewsItem(models.Model): - date = models.DateField() - headline = models.CharField(max_length=300) - blurb = models.TextField() - followup_link = models.URLField(blank=True, null=True) - followup_link_text = models.CharField(max_length=300, blank=True, null=True) - - def __str__(self): - return self.date.strftime('%Y-%m-%d') + ', ' + self.headline - - def descriptor_full(self): - """ For News page. """ - descriptor = ('<div class="flex-greybox640">' - '<h3 class="NewsHeadline">{{ headline }}</h3>' - '<p>{{ date }}</p>' - '<p>{{ blurb }}</p>' - ) - context = Context({'headline': self.headline, - 'date': self.date.strftime('%Y-%m-%d'), - 'blurb': self.blurb, }) - if self.followup_link: - descriptor += '<p><a href="{{ followup_link }}">{{ followup_link_text }}</a></p>' - context['followup_link'] = self.followup_link - context['followup_link_text'] = self.followup_link_text - descriptor += '</div>' - template = Template(descriptor) - return template.render(context) - - def descriptor_small(self): - """ For index page. """ - descriptor = ('<h3 class="NewsHeadline">{{ headline }}</h3>' - '<div class="p-2">' - '<p>{{ date }}</p>' - '<p>{{ blurb }}</p>' - ) - context = Context({'headline': self.headline, - 'date': self.date.strftime('%Y-%m-%d'), - 'blurb': self.blurb, }) - if self.followup_link: - descriptor += '<p><a href="{{ followup_link }}">{{ followup_link_text }}</a></p>' - context['followup_link'] = self.followup_link - context['followup_link_text'] = self.followup_link_text - descriptor += '</div>' - template = Template(descriptor) - return template.render(context) - - ##################################### # Virtual General Meetings, Motions # ##################################### -class VGM(models.Model): - """ - Each year, a Virtual General Meeting is held during which operations at - SciPost are discussed. A VGM can be attended by Administrators, - Advisory Board members and Editorial Fellows. - """ - start_date = models.DateField() - end_date = models.DateField() - information = models.TextField(default='') - - def __str__(self): - return 'From %s to %s' % (self.start_date.strftime('%Y-%m-%d'), - self.end_date.strftime('%Y-%m-%d')) - - -class Feedback(models.Model): - """ - Feedback, suggestion or criticism on any aspect of SciPost. - """ - VGM = models.ForeignKey(VGM, blank=True, null=True) - by = models.ForeignKey(Contributor) - date = models.DateField() - feedback = models.TextField() - - def __str__(self): - return '%s: %s' % (self.by, self.feedback[:50]) - - def as_li(self): - html = ('<div class="Feedback">' - '<h3><em>by {{ by }}</em></h3>' - '<p>{{ feedback|linebreaks }}</p>' - '</div>') - context = Context({ - 'feedback': self.feedback, - 'by': '%s %s' % (self.by.user.first_name, - self.by.user.last_name)}) - template = Template(html) - return template.render(context) - - -class Nomination(models.Model): - """ - Nomination to an Editorial Fellowship. - """ - VGM = models.ForeignKey(VGM, blank=True, null=True) - by = models.ForeignKey(Contributor) - date = models.DateField() - first_name = models.CharField(max_length=30, default='') - last_name = models.CharField(max_length=30, default='') - discipline = models.CharField(max_length=20, choices=SCIPOST_DISCIPLINES, - default='physics', verbose_name='Main discipline') - expertises = ChoiceArrayField( - models.CharField(max_length=10, choices=SCIPOST_SUBJECT_AREAS), - blank=True, null=True) - webpage = models.URLField(default='') - nr_A = models.PositiveIntegerField(default=0) - in_agreement = models.ManyToManyField(Contributor, - related_name='in_agreement_with_nomination', blank=True) - nr_N = models.PositiveIntegerField(default=0) - in_notsure = models.ManyToManyField(Contributor, - related_name='in_notsure_with_nomination', blank=True) - nr_D = models.PositiveIntegerField(default=0) - in_disagreement = models.ManyToManyField(Contributor, - related_name='in_disagreement_with_nomination', - blank=True) - voting_deadline = models.DateTimeField('voting deadline', default=timezone.now) - accepted = models.NullBooleanField() - - def __str__(self): - return '%s %s (nominated by %s)' % (self.first_name, - self.last_name, - self.by) - - def as_li(self): - html = ('<div class="Nomination" id="nomination_id{{ nomination_id }}" ' - 'style="background-color: #eeeeee;">' - '<div class="row">' - '<div class="col-4">' - '<h3><em> {{ name }}</em></h3>' - '<p>Nominated by {{ proposer }}</p>' - '</div>' - '<div class="col-4">' - '<p><a href="{{ webpage }}">Webpage</a></p>' - '<p>Discipline: {{ discipline }}</p></div>' - '<div class="col-4"><p>expertise:<ul>') - for exp in self.expertises: - html += '<li>%s</li>' % subject_areas_dict[exp] - html += '</ul></div></div></div>' - context = Context({ - 'nomination_id': self.id, - 'proposer': '%s %s' % (self.by.user.first_name, - self.by.user.last_name), - 'name': self.first_name + ' ' + self.last_name, - 'discipline': disciplines_dict[self.discipline], - 'webpage': self.webpage, - }) - template = Template(html) - return template.render(context) - - def votes_as_ul(self): - template = Template(''' - <ul class="opinionsDisplay"> - <li style="background-color: #000099">Agree {{ nr_A }}</li> - <li style="background-color: #555555">Abstain {{ nr_N }}</li> - <li style="background-color: #990000">Disagree {{ nr_D }}</li> - </ul> - ''') - context = Context({'nr_A': self.nr_A, 'nr_N': self.nr_N, 'nr_D': self.nr_D}) - return template.render(context) - - def update_votes(self, contributor_id, vote): - contributor = get_object_or_404(Contributor, pk=contributor_id) - self.in_agreement.remove(contributor) - self.in_notsure.remove(contributor) - self.in_disagreement.remove(contributor) - if vote == 'A': - self.in_agreement.add(contributor) - elif vote == 'N': - self.in_notsure.add(contributor) - elif vote == 'D': - self.in_disagreement.add(contributor) - self.nr_A = self.in_agreement.count() - self.nr_N = self.in_notsure.count() - self.nr_D = self.in_disagreement.count() - self.save() - - -MOTION_CATEGORIES = ( - ('ByLawAmend', 'Amendments to by-laws'), - ('Workflow', 'Editorial workflow improvements'), - ('General', 'General'), -) -motion_categories_dict = dict(MOTION_CATEGORIES) - - -class Motion(models.Model): - """ - Motion instances are put forward to the Advisory Board and Editorial College - and detail suggested changes to rules, procedures etc. - They are meant to be voted on at the annual VGM. - """ - category = models.CharField(max_length=10, choices=MOTION_CATEGORIES, - default='General') - VGM = models.ForeignKey(VGM, blank=True, null=True) - background = models.TextField() - motion = models.TextField() - put_forward_by = models.ForeignKey(Contributor) - date = models.DateField() - nr_A = models.PositiveIntegerField(default=0) - in_agreement = models.ManyToManyField(Contributor, - related_name='in_agreement_with_motion', blank=True) - nr_N = models.PositiveIntegerField(default=0) - in_notsure = models.ManyToManyField(Contributor, - related_name='in_notsure_with_motion', blank=True) - nr_D = models.PositiveIntegerField(default=0) - in_disagreement = models.ManyToManyField(Contributor, - related_name='in_disagreement_with_motion', - blank=True) - voting_deadline = models.DateTimeField('voting deadline', default=timezone.now) - accepted = models.NullBooleanField() - - def __str__(self): - return self.motion[:32] - - def as_li(self): - html = ('<div class="Motion" id="motion_id{{ motion_id }}">' - '<h3><em>Motion {{ motion_id }}, put forward by {{ proposer }}</em></h3>' - '<h3>Background:</h3><p>{{ background|linebreaks }}</p>' - '<h3>Motion:</h3>' - '<div class="flex-container"><div class="flex-greybox">' - '<p style="background-color: #eeeeee;">{{ motion|linebreaks }}</p>' - '</div></div>' - '</div>') - context = Context({ - 'motion_id': self.id, - 'proposer': '%s %s' % (self.put_forward_by.user.first_name, - self.put_forward_by.user.last_name), - 'background': self.background, - 'motion': self.motion, }) - template = Template(html) - return template.render(context) - - def votes_as_ul(self): - template = Template(''' - <ul class="opinionsDisplay"> - <li style="background-color: #000099">Agree {{ nr_A }}</li> - <li style="background-color: #555555">Abstain {{ nr_N }}</li> - <li style="background-color: #990000">Disagree {{ nr_D }}</li> - </ul> - ''') - context = Context({'nr_A': self.nr_A, 'nr_N': self.nr_N, 'nr_D': self.nr_D}) - return template.render(context) - - def update_votes(self, contributor_id, vote): - contributor = get_object_or_404(Contributor, pk=contributor_id) - self.in_agreement.remove(contributor) - self.in_notsure.remove(contributor) - self.in_disagreement.remove(contributor) - if vote == 'A': - self.in_agreement.add(contributor) - elif vote == 'N': - self.in_notsure.add(contributor) - elif vote == 'D': - self.in_disagreement.add(contributor) - self.nr_A = self.in_agreement.count() - self.nr_N = self.in_notsure.count() - self.nr_D = self.in_disagreement.count() - self.save() +# class VGM(models.Model): +# """ +# Each year, a Virtual General Meeting is held during which operations at +# SciPost are discussed. A VGM can be attended by Administrators, +# Advisory Board members and Editorial Fellows. +# """ +# start_date = models.DateField() +# end_date = models.DateField() +# information = models.TextField(default='') +# +# def __str__(self): +# return 'From %s to %s' % (self.start_date.strftime('%Y-%m-%d'), +# self.end_date.strftime('%Y-%m-%d')) +# +# +# class Feedback(models.Model): +# """ +# Feedback, suggestion or criticism on any aspect of SciPost. +# """ +# VGM = models.ForeignKey(VGM, blank=True, null=True) +# by = models.ForeignKey(Contributor) +# date = models.DateField() +# feedback = models.TextField() +# +# def __str__(self): +# return '%s: %s' % (self.by, self.feedback[:50]) +# +# def as_li(self): +# html = ('<div class="Feedback">' +# '<h3><em>by {{ by }}</em></h3>' +# '<p>{{ feedback|linebreaks }}</p>' +# '</div>') +# context = Context({ +# 'feedback': self.feedback, +# 'by': '%s %s' % (self.by.user.first_name, +# self.by.user.last_name)}) +# template = Template(html) +# return template.render(context) +# +# +# class Nomination(models.Model): +# """ +# Nomination to an Editorial Fellowship. +# """ +# VGM = models.ForeignKey(VGM, blank=True, null=True) +# by = models.ForeignKey(Contributor) +# date = models.DateField() +# first_name = models.CharField(max_length=30, default='') +# last_name = models.CharField(max_length=30, default='') +# discipline = models.CharField(max_length=20, choices=SCIPOST_DISCIPLINES, +# default='physics', verbose_name='Main discipline') +# expertises = ChoiceArrayField( +# models.CharField(max_length=10, choices=SCIPOST_SUBJECT_AREAS), +# blank=True, null=True) +# webpage = models.URLField(default='') +# nr_A = models.PositiveIntegerField(default=0) +# in_agreement = models.ManyToManyField(Contributor, +# related_name='in_agreement_with_nomination', blank=True) +# nr_N = models.PositiveIntegerField(default=0) +# in_notsure = models.ManyToManyField(Contributor, +# related_name='in_notsure_with_nomination', blank=True) +# nr_D = models.PositiveIntegerField(default=0) +# in_disagreement = models.ManyToManyField(Contributor, +# related_name='in_disagreement_with_nomination', +# blank=True) +# voting_deadline = models.DateTimeField('voting deadline', default=timezone.now) +# accepted = models.NullBooleanField() +# +# def __str__(self): +# return '%s %s (nominated by %s)' % (self.first_name, +# self.last_name, +# self.by) +# +# def as_li(self): +# html = ('<div class="Nomination" id="nomination_id{{ nomination_id }}" ' +# 'style="background-color: #eeeeee;">' +# '<div class="row">' +# '<div class="col-4">' +# '<h3><em> {{ name }}</em></h3>' +# '<p>Nominated by {{ proposer }}</p>' +# '</div>' +# '<div class="col-4">' +# '<p><a href="{{ webpage }}">Webpage</a></p>' +# '<p>Discipline: {{ discipline }}</p></div>' +# '<div class="col-4"><p>expertise:<ul>') +# for exp in self.expertises: +# html += '<li>%s</li>' % subject_areas_dict[exp] +# html += '</ul></div></div></div>' +# context = Context({ +# 'nomination_id': self.id, +# 'proposer': '%s %s' % (self.by.user.first_name, +# self.by.user.last_name), +# 'name': self.first_name + ' ' + self.last_name, +# 'discipline': disciplines_dict[self.discipline], +# 'webpage': self.webpage, +# }) +# template = Template(html) +# return template.render(context) +# +# def votes_as_ul(self): +# template = Template(''' +# <ul class="opinionsDisplay"> +# <li style="background-color: #000099">Agree {{ nr_A }}</li> +# <li style="background-color: #555555">Abstain {{ nr_N }}</li> +# <li style="background-color: #990000">Disagree {{ nr_D }}</li> +# </ul> +# ''') +# context = Context({'nr_A': self.nr_A, 'nr_N': self.nr_N, 'nr_D': self.nr_D}) +# return template.render(context) +# +# def update_votes(self, contributor_id, vote): +# contributor = get_object_or_404(Contributor, pk=contributor_id) +# self.in_agreement.remove(contributor) +# self.in_notsure.remove(contributor) +# self.in_disagreement.remove(contributor) +# if vote == 'A': +# self.in_agreement.add(contributor) +# elif vote == 'N': +# self.in_notsure.add(contributor) +# elif vote == 'D': +# self.in_disagreement.add(contributor) +# self.nr_A = self.in_agreement.count() +# self.nr_N = self.in_notsure.count() +# self.nr_D = self.in_disagreement.count() +# self.save() +# +# +# MOTION_CATEGORIES = ( +# ('ByLawAmend', 'Amendments to by-laws'), +# ('Workflow', 'Editorial workflow improvements'), +# ('General', 'General'), +# ) +# motion_categories_dict = dict(MOTION_CATEGORIES) +# +# +# class Motion(models.Model): +# """ +# Motion instances are put forward to the Advisory Board and Editorial College +# and detail suggested changes to rules, procedures etc. +# They are meant to be voted on at the annual VGM. +# """ +# category = models.CharField(max_length=10, choices=MOTION_CATEGORIES, +# default='General') +# VGM = models.ForeignKey(VGM, blank=True, null=True) +# background = models.TextField() +# motion = models.TextField() +# put_forward_by = models.ForeignKey(Contributor) +# date = models.DateField() +# nr_A = models.PositiveIntegerField(default=0) +# in_agreement = models.ManyToManyField(Contributor, +# related_name='in_agreement_with_motion', blank=True) +# nr_N = models.PositiveIntegerField(default=0) +# in_notsure = models.ManyToManyField(Contributor, +# related_name='in_notsure_with_motion', blank=True) +# nr_D = models.PositiveIntegerField(default=0) +# in_disagreement = models.ManyToManyField(Contributor, +# related_name='in_disagreement_with_motion', +# blank=True) +# voting_deadline = models.DateTimeField('voting deadline', default=timezone.now) +# accepted = models.NullBooleanField() +# +# def __str__(self): +# return self.motion[:32] +# +# def as_li(self): +# html = ('<div class="Motion" id="motion_id{{ motion_id }}">' +# '<h3><em>Motion {{ motion_id }}, put forward by {{ proposer }}</em></h3>' +# '<h3>Background:</h3><p>{{ background|linebreaks }}</p>' +# '<h3>Motion:</h3>' +# '<div class="flex-container"><div class="flex-greybox">' +# '<p style="background-color: #eeeeee;">{{ motion|linebreaks }}</p>' +# '</div></div>' +# '</div>') +# context = Context({ +# 'motion_id': self.id, +# 'proposer': '%s %s' % (self.put_forward_by.user.first_name, +# self.put_forward_by.user.last_name), +# 'background': self.background, +# 'motion': self.motion, }) +# template = Template(html) +# return template.render(context) +# +# def votes_as_ul(self): +# template = Template(''' +# <ul class="opinionsDisplay"> +# <li style="background-color: #000099">Agree {{ nr_A }}</li> +# <li style="background-color: #555555">Abstain {{ nr_N }}</li> +# <li style="background-color: #990000">Disagree {{ nr_D }}</li> +# </ul> +# ''') +# context = Context({'nr_A': self.nr_A, 'nr_N': self.nr_N, 'nr_D': self.nr_D}) +# return template.render(context) +# +# def update_votes(self, contributor_id, vote): +# contributor = get_object_or_404(Contributor, pk=contributor_id) +# self.in_agreement.remove(contributor) +# self.in_notsure.remove(contributor) +# self.in_disagreement.remove(contributor) +# if vote == 'A': +# self.in_agreement.add(contributor) +# elif vote == 'N': +# self.in_notsure.add(contributor) +# elif vote == 'D': +# self.in_disagreement.add(contributor) +# self.nr_A = self.in_agreement.count() +# self.nr_N = self.in_notsure.count() +# self.nr_D = self.in_disagreement.count() +# self.save() ######### diff --git a/scipost/templates/scipost/index.html b/scipost/templates/scipost/index.html index 8cf87d0cdf9e1ee8e18750bca8be04c57df437d2..13a32d48dfa43b1cd37fb8e0d6af8bb5c5fa36cc 100644 --- a/scipost/templates/scipost/index.html +++ b/scipost/templates/scipost/index.html @@ -9,7 +9,7 @@ {% if latest_newsitems %} <div class="col-md-6 {% if user.is_authenticated %}col-lg-4{% else %}col-lg-3{% endif %}"> <div class="panel"> - <h1><a href="{% url 'scipost:news' %}">News</a><a style="float: right;" href="{% url 'scipost:feeds' %}"><img src="{% static 'scipost/images/feed-icon-14x14.png' %}" alt="Feed logo" width="14"></a></h1> + <h1><a href="{% url 'news:news' %}">News</a><a style="float: right;" href="{% url 'scipost:feeds' %}"><img src="{% static 'scipost/images/feed-icon-14x14.png' %}" alt="Feed logo" width="14"></a></h1> <p>Latest news and announcements.</p> {# <hr class="hr6"/>#} <ul class="NewsItemsList"> diff --git a/scipost/urls.py b/scipost/urls.py index e3b8c573147bbafd6e2541cce29bbeee3d68279e..1ed0f1b6a55fccc4f199d49d5b767102ec62833b 100644 --- a/scipost/urls.py +++ b/scipost/urls.py @@ -17,7 +17,6 @@ urlpatterns = [ name='acknowledgement'), ## Info - url(r'^news$', views.news, name='news'), url(r'^about$', TemplateView.as_view(template_name='scipost/about.html'), name='about'), url(r'^call$', TemplateView.as_view(template_name='scipost/call.html'), name='call'), url(r'^foundation$', TemplateView.as_view(template_name='scipost/foundation.html'), diff --git a/scipost/views.py b/scipost/views.py index 78635541dc0746e2cb539ba7308640f21686acfa..496ceb09a3032657e7148518705a682a1aa36d56 100644 --- a/scipost/views.py +++ b/scipost/views.py @@ -27,11 +27,10 @@ from guardian.shortcuts import assign_perm from .constants import SCIPOST_SUBJECT_AREAS from .models import Contributor, CitationNotification, UnavailabilityPeriod,\ - DraftInvitation, RegistrationInvitation, NewsItem,\ + DraftInvitation, RegistrationInvitation, Remark,\ List, Team, Graph, Node, Arc,\ title_dict, SciPost_from_addresses_dict,\ - AuthorshipClaim, SupportingPartner, SPBMembershipAgreement,\ - VGM, Feedback, Nomination, Remark, Motion, motion_categories_dict + AuthorshipClaim, SupportingPartner, SPBMembershipAgreement from .forms import AuthenticationForm, DraftInvitationForm, UnavailabilityPeriodForm,\ RegistrationForm, RegistrationInvitationForm, AuthorshipClaimForm,\ ModifyPersonalMessageForm, SearchForm, VetRegistrationForm, reg_ref_dict,\ @@ -48,12 +47,15 @@ from commentaries.models import Commentary from commentaries.forms import CommentarySearchForm from comments.models import Comment from journals.models import Publication +from news.models import NewsItem from submissions.models import SUBMISSION_STATUS_PUBLICLY_UNLISTED from submissions.models import Submission, EditorialAssignment from submissions.models import RefereeInvitation, Report, EICRecommendation from submissions.forms import SubmissionSearchForm from theses.models import ThesisLink from theses.forms import ThesisLinkSearchForm +from virtualmeetings.models import VGM, Feedback, Nomination, Motion +from virtualmeetings.constants import motion_categories_dict ############## @@ -232,12 +234,6 @@ def base(request): return render(request, 'scipost/base.html') -def news(request): - newsitems = NewsItem.objects.all().order_by('-date') - context = {'newsitems': newsitems} - return render(request, 'scipost/news.html', context) - - def feeds(request): context = {'subject_areas_physics': SCIPOST_SUBJECT_AREAS[0][1]} return render(request, 'scipost/feeds.html', context) diff --git a/virtualmeetings/__init__.py b/virtualmeetings/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/virtualmeetings/admin.py b/virtualmeetings/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..c831a447b58b6d95edd086fda5750bbfc5e315c6 --- /dev/null +++ b/virtualmeetings/admin.py @@ -0,0 +1,31 @@ +from django.contrib import admin + +from .models import VGM, Feedback, Nomination, Motion + + +class VGMAdmin(admin.ModelAdmin): + search_fields = ['start_date'] + + +admin.site.register(VGM, VGMAdmin) + + +class FeedbackAdmin(admin.ModelAdmin): + search_fields = ['feedback', 'by'] + + +admin.site.register(Feedback, FeedbackAdmin) + + +class NominationAdmin(admin.ModelAdmin): + search_fields = ['last_name', 'first_name', 'by'] + + +admin.site.register(Nomination, NominationAdmin) + + +class MotionAdmin(admin.ModelAdmin): + search_fields = ['background', 'motion', 'put_forward_by'] + + +admin.site.register(Motion, MotionAdmin) diff --git a/virtualmeetings/apps.py b/virtualmeetings/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..9bbfd4ea04576759b428cbd1b1127a2767b82216 --- /dev/null +++ b/virtualmeetings/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class VirtualmeetingsConfig(AppConfig): + name = 'virtualmeetings' diff --git a/virtualmeetings/constants.py b/virtualmeetings/constants.py new file mode 100644 index 0000000000000000000000000000000000000000..f75281f3af9bd47307eb81ce6542ae3b89b8ffc8 --- /dev/null +++ b/virtualmeetings/constants.py @@ -0,0 +1,9 @@ +MOTION_AMENDMENTS = 'ByLawAmend' +MOTION_WORKFLOW = 'Workflow' +MOTION_GENERAL = 'General' +MOTION_CATEGORIES = ( + (MOTION_AMENDMENTS, 'Amendments to by-laws'), + (MOTION_WORKFLOW, 'Editorial workflow improvements'), + (MOTION_GENERAL, 'General'), +) +motion_categories_dict = dict(MOTION_CATEGORIES) diff --git a/virtualmeetings/forms.py b/virtualmeetings/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..54fb4a68e81dfe0f7a1f54be8405a5ae5cfe60e4 --- /dev/null +++ b/virtualmeetings/forms.py @@ -0,0 +1,61 @@ +from django import forms + +from crispy_forms.helper import FormHelper +from crispy_forms.layout import Layout, Div, Field, HTML, Submit + +from .models import Feedback, Nomination, Motion + +from scipost.constants import SCIPOST_SUBJECT_AREAS + + +class FeedbackForm(forms.ModelForm): + class Meta: + model = Feedback + fields = ['feedback'] + + +class NominationForm(forms.ModelForm): + class Meta: + model = Nomination + fields = ['first_name', 'last_name', + 'discipline', 'expertises', 'webpage'] + + def __init__(self, *args, **kwargs): + super(NominationForm, self).__init__(*args, **kwargs) + self.fields['expertises'].widget = forms.SelectMultiple(choices=SCIPOST_SUBJECT_AREAS) + + +class MotionForm(forms.ModelForm): + class Meta: + model = Motion + fields = ['category', 'background', 'motion'] + + def __init__(self, *args, **kwargs): + super(MotionForm, self).__init__(*args, **kwargs) + self.fields['background'].label = '' + self.fields['background'].widget.attrs.update( + {'rows': 8, 'cols': 100, + 'placeholder': 'Provide useful background information on your Motion.'}) + self.fields['motion'].label = '' + self.fields['motion'].widget.attrs.update( + {'rows': 8, 'cols': 100, + 'placeholder': 'Phrase your Motion as clearly and succinctly as possible.'}) + self.helper = FormHelper() + self.helper.layout = Layout( + Field('category'), + Div( + Div(HTML('<p>Background:</p>'), + css_class="col-2"), + Div( + Field('background'), + css_class="col-10"), + css_class="row"), + Div( + Div(HTML('<p>Motion:</p>'), + css_class="col-2"), + Div( + Field('motion'), + css_class="col-10"), + css_class="row"), + Submit('submit', 'Submit'), + ) diff --git a/virtualmeetings/migrations/0001_initial.py b/virtualmeetings/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..567764e57de6f7ee69c5fa158016d95f64917c0a --- /dev/null +++ b/virtualmeetings/migrations/0001_initial.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-03-06 07:04 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import scipost.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('scipost', '0039_auto_20170306_0804'), + ] + + state_operations = [ + migrations.CreateModel( + name='Feedback', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateField()), + ('feedback', models.TextField()), + ], + options={ + 'db_table': 'scipost_feedback', + }, + ), + migrations.CreateModel( + name='Motion', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('category', models.CharField(choices=[('ByLawAmend', 'Amendments to by-laws'), ('Workflow', 'Editorial workflow improvements'), ('General', 'General')], default='General', max_length=10)), + ('background', models.TextField()), + ('motion', models.TextField()), + ('date', models.DateField()), + ('nr_A', models.PositiveIntegerField(default=0)), + ('nr_N', models.PositiveIntegerField(default=0)), + ('nr_D', models.PositiveIntegerField(default=0)), + ('voting_deadline', models.DateTimeField(default=django.utils.timezone.now, verbose_name='voting deadline')), + ('accepted', models.NullBooleanField()), + ], + options={ + 'db_table': 'scipost_motion', + }, + ), + migrations.CreateModel( + name='Nomination', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateField()), + ('first_name', models.CharField(default='', max_length=30)), + ('last_name', models.CharField(default='', max_length=30)), + ('discipline', models.CharField(choices=[('physics', 'Physics'), ('astrophysics', 'Astrophysics'), ('mathematics', 'Mathematics'), ('computerscience', 'Computer Science')], default='physics', max_length=20, verbose_name='Main discipline')), + ('expertises', scipost.models.ChoiceArrayField(base_field=models.CharField(choices=[('Physics', (('Phys:AE', 'Atomic, Molecular and Optical Physics - Experiment'), ('Phys:AT', 'Atomic, Molecular and Optical Physics - Theory'), ('Phys:BI', 'Biophysics'), ('Phys:CE', 'Condensed Matter Physics - Experiment'), ('Phys:CT', 'Condensed Matter Physics - Theory'), ('Phys:FD', 'Fluid Dynamics'), ('Phys:GR', 'Gravitation, Cosmology and Astroparticle Physics'), ('Phys:HE', 'High-Energy Physics - Experiment'), ('Phys:HT', 'High-Energy Physics- Theory'), ('Phys:HP', 'High-Energy Physics - Phenomenology'), ('Phys:MP', 'Mathematical Physics'), ('Phys:NE', 'Nuclear Physics - Experiment'), ('Phys:NT', 'Nuclear Physics - Theory'), ('Phys:QP', 'Quantum Physics'), ('Phys:SM', 'Statistical and Soft Matter Physics'))), ('Astrophysics', (('Astro:GA', 'Astrophysics of Galaxies'), ('Astro:CO', 'Cosmology and Nongalactic Astrophysics'), ('Astro:EP', 'Earth and Planetary Astrophysics'), ('Astro:HE', 'High Energy Astrophysical Phenomena'), ('Astro:IM', 'Instrumentation and Methods for Astrophysics'), ('Astro:SR', 'Solar and Stellar Astrophysics'))), ('Mathematics', (('Math:AG', 'Algebraic Geometry'), ('Math:AT', 'Algebraic Topology'), ('Math:AP', 'Analysis of PDEs'), ('Math:CT', 'Category Theory'), ('Math:CA', 'Classical Analysis and ODEs'), ('Math:CO', 'Combinatorics'), ('Math:AC', 'Commutative Algebra'), ('Math:CV', 'Complex Variables'), ('Math:DG', 'Differential Geometry'), ('Math:DS', 'Dynamical Systems'), ('Math:FA', 'Functional Analysis'), ('Math:GM', 'General Mathematics'), ('Math:GN', 'General Topology'), ('Math:GT', 'Geometric Topology'), ('Math:GR', 'Group Theory'), ('Math:HO', 'History and Overview'), ('Math:IT', 'Information Theory'), ('Math:KT', 'K-Theory and Homology'), ('Math:LO', 'Logic'), ('Math:MP', 'Mathematical Physics'), ('Math:MG', 'Metric Geometry'), ('Math:NT', 'Number Theory'), ('Math:NA', 'Numerical Analysis'), ('Math:OA', 'Operator Algebras'), ('Math:OC', 'Optimization and Control'), ('Math:PR', 'Probability'), ('Math:QA', 'Quantum Algebra'), ('Math:RT', 'Representation Theory'), ('Math:RA', 'Rings and Algebras'), ('Math:SP', 'Spectral Theory'), ('Math:ST', 'Statistics Theory'), ('Math:SG', 'Symplectic Geometry'))), ('Computer Science', (('Comp:AI', 'Artificial Intelligence'), ('Comp:CC', 'Computational Complexity'), ('Comp:CE', 'Computational Engineering, Finance, and Science'), ('Comp:CG', 'Computational Geometry'), ('Comp:GT', 'Computer Science and Game Theory'), ('Comp:CV', 'Computer Vision and Pattern Recognition'), ('Comp:CY', 'Computers and Society'), ('Comp:CR', 'Cryptography and Security'), ('Comp:DS', 'Data Structures and Algorithms'), ('Comp:DB', 'Databases'), ('Comp:DL', 'Digital Libraries'), ('Comp:DM', 'Discrete Mathematics'), ('Comp:DC', 'Distributed, Parallel, and Cluster Computing'), ('Comp:ET', 'Emerging Technologies'), ('Comp:FL', 'Formal Languages and Automata Theory'), ('Comp:GL', 'General Literature'), ('Comp:GR', 'Graphics'), ('Comp:AR', 'Hardware Architecture'), ('Comp:HC', 'Human-Computer Interaction'), ('Comp:IR', 'Information Retrieval'), ('Comp:IT', 'Information Theory'), ('Comp:LG', 'Learning'), ('Comp:LO', 'Logic in Computer Science'), ('Comp:MS', 'Mathematical Software'), ('Comp:MA', 'Multiagent Systems'), ('Comp:MM', 'Multimedia'), ('Comp:NI', 'Networking and Internet Architecture'), ('Comp:NE', 'Neural and Evolutionary Computing'), ('Comp:NA', 'Numerical Analysis'), ('Comp:OS', 'Operating Systems'), ('Comp:OH', 'Other Computer Science'), ('Comp:PF', 'Performance'), ('Comp:PL', 'Programming Languages'), ('Comp:RO', 'Robotics'), ('Comp:SI', 'Social and Information Networks'), ('Comp:SE', 'Software Engineering'), ('Comp:SD', 'Sound'), ('Comp:SC', 'Symbolic Computation'), ('Comp:SY', 'Systems and Control')))], max_length=10), blank=True, null=True, size=None)), + ('webpage', models.URLField(default='')), + ('nr_A', models.PositiveIntegerField(default=0)), + ('nr_N', models.PositiveIntegerField(default=0)), + ('nr_D', models.PositiveIntegerField(default=0)), + ('voting_deadline', models.DateTimeField(default=django.utils.timezone.now, verbose_name='voting deadline')), + ('accepted', models.NullBooleanField()), + ], + options={ + 'db_table': 'scipost_nomination', + }, + ), + migrations.CreateModel( + name='VGM', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('start_date', models.DateField()), + ('end_date', models.DateField()), + ('information', models.TextField(default='')), + ], + options={ + 'db_table': 'scipost_vgm', + }, + ), + migrations.AddField( + model_name='nomination', + name='VGM', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='virtualmeetings.VGM'), + ), + migrations.AddField( + model_name='nomination', + name='by', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='scipost.Contributor'), + ), + migrations.AddField( + model_name='nomination', + name='in_agreement', + field=models.ManyToManyField(blank=True, related_name='in_agreement_with_nomination', to='scipost.Contributor'), + ), + migrations.AddField( + model_name='nomination', + name='in_disagreement', + field=models.ManyToManyField(blank=True, related_name='in_disagreement_with_nomination', to='scipost.Contributor'), + ), + migrations.AddField( + model_name='nomination', + name='in_notsure', + field=models.ManyToManyField(blank=True, related_name='in_notsure_with_nomination', to='scipost.Contributor'), + ), + migrations.AddField( + model_name='motion', + name='VGM', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='virtualmeetings.VGM'), + ), + migrations.AddField( + model_name='motion', + name='in_agreement', + field=models.ManyToManyField(blank=True, related_name='in_agreement_with_motion', to='scipost.Contributor'), + ), + migrations.AddField( + model_name='motion', + name='in_disagreement', + field=models.ManyToManyField(blank=True, related_name='in_disagreement_with_motion', to='scipost.Contributor'), + ), + migrations.AddField( + model_name='motion', + name='in_notsure', + field=models.ManyToManyField(blank=True, related_name='in_notsure_with_motion', to='scipost.Contributor'), + ), + migrations.AddField( + model_name='motion', + name='put_forward_by', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='scipost.Contributor'), + ), + migrations.AddField( + model_name='feedback', + name='VGM', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='virtualmeetings.VGM'), + ), + migrations.AddField( + model_name='feedback', + name='by', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='scipost.Contributor'), + ), + ] + + operations = [ + migrations.SeparateDatabaseAndState(state_operations=state_operations) + ] diff --git a/virtualmeetings/migrations/__init__.py b/virtualmeetings/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/virtualmeetings/models.py b/virtualmeetings/models.py new file mode 100644 index 0000000000000000000000000000000000000000..55aaacefe039c513e6f0a63644e44d2d4f59c46c --- /dev/null +++ b/virtualmeetings/models.py @@ -0,0 +1,223 @@ +from django.db import models +from django.shortcuts import get_object_or_404 +from django.template import Context, Template +from django.utils import timezone + +from .constants import MOTION_CATEGORIES + +from scipost.constants import SCIPOST_DISCIPLINES, SCIPOST_SUBJECT_AREAS,\ + subject_areas_dict, disciplines_dict +from scipost.models import Contributor, ChoiceArrayField + + +class VGM(models.Model): + """ + Each year, a Virtual General Meeting is held during which operations at + SciPost are discussed. A VGM can be attended by Administrators, + Advisory Board members and Editorial Fellows. + """ + start_date = models.DateField() + end_date = models.DateField() + information = models.TextField(default='') + + class Meta: + db_table = 'scipost_vgm' + + def __str__(self): + return 'From %s to %s' % (self.start_date.strftime('%Y-%m-%d'), + self.end_date.strftime('%Y-%m-%d')) + + +class Feedback(models.Model): + """ + Feedback, suggestion or criticism on any aspect of SciPost. + """ + VGM = models.ForeignKey(VGM, blank=True, null=True) + by = models.ForeignKey(Contributor) + date = models.DateField() + feedback = models.TextField() + + class Meta: + db_table = 'scipost_feedback' + + def __str__(self): + return '%s: %s' % (self.by, self.feedback[:50]) + + def as_li(self): + html = ('<div class="Feedback">' + '<h3><em>by {{ by }}</em></h3>' + '<p>{{ feedback|linebreaks }}</p>' + '</div>') + context = Context({ + 'feedback': self.feedback, + 'by': '%s %s' % (self.by.user.first_name, + self.by.user.last_name)}) + template = Template(html) + return template.render(context) + + +class Nomination(models.Model): + """ + Nomination to an Editorial Fellowship. + """ + VGM = models.ForeignKey(VGM, blank=True, null=True) + by = models.ForeignKey(Contributor) + date = models.DateField() + first_name = models.CharField(max_length=30, default='') + last_name = models.CharField(max_length=30, default='') + discipline = models.CharField(max_length=20, choices=SCIPOST_DISCIPLINES, + default='physics', verbose_name='Main discipline') + expertises = ChoiceArrayField( + models.CharField(max_length=10, choices=SCIPOST_SUBJECT_AREAS), + blank=True, null=True) + webpage = models.URLField(default='') + nr_A = models.PositiveIntegerField(default=0) + in_agreement = models.ManyToManyField(Contributor, + related_name='in_agreement_with_nomination', blank=True) + nr_N = models.PositiveIntegerField(default=0) + in_notsure = models.ManyToManyField(Contributor, + related_name='in_notsure_with_nomination', blank=True) + nr_D = models.PositiveIntegerField(default=0) + in_disagreement = models.ManyToManyField(Contributor, + related_name='in_disagreement_with_nomination', + blank=True) + voting_deadline = models.DateTimeField('voting deadline', default=timezone.now) + accepted = models.NullBooleanField() + + class Meta: + db_table = 'scipost_nomination' + + def __str__(self): + return '%s %s (nominated by %s)' % (self.first_name, + self.last_name, + self.by) + + def as_li(self): + html = ('<div class="Nomination" id="nomination_id{{ nomination_id }}" ' + 'style="background-color: #eeeeee;">' + '<div class="row">' + '<div class="col-4">' + '<h3><em> {{ name }}</em></h3>' + '<p>Nominated by {{ proposer }}</p>' + '</div>' + '<div class="col-4">' + '<p><a href="{{ webpage }}">Webpage</a></p>' + '<p>Discipline: {{ discipline }}</p></div>' + '<div class="col-4"><p>expertise:<ul>') + for exp in self.expertises: + html += '<li>%s</li>' % subject_areas_dict[exp] + html += '</ul></div></div></div>' + context = Context({ + 'nomination_id': self.id, + 'proposer': '%s %s' % (self.by.user.first_name, + self.by.user.last_name), + 'name': self.first_name + ' ' + self.last_name, + 'discipline': disciplines_dict[self.discipline], + 'webpage': self.webpage, + }) + template = Template(html) + return template.render(context) + + def votes_as_ul(self): + template = Template(''' + <ul class="opinionsDisplay"> + <li style="background-color: #000099">Agree {{ nr_A }}</li> + <li style="background-color: #555555">Abstain {{ nr_N }}</li> + <li style="background-color: #990000">Disagree {{ nr_D }}</li> + </ul> + ''') + context = Context({'nr_A': self.nr_A, 'nr_N': self.nr_N, 'nr_D': self.nr_D}) + return template.render(context) + + def update_votes(self, contributor_id, vote): + contributor = get_object_or_404(Contributor, pk=contributor_id) + self.in_agreement.remove(contributor) + self.in_notsure.remove(contributor) + self.in_disagreement.remove(contributor) + if vote == 'A': + self.in_agreement.add(contributor) + elif vote == 'N': + self.in_notsure.add(contributor) + elif vote == 'D': + self.in_disagreement.add(contributor) + self.nr_A = self.in_agreement.count() + self.nr_N = self.in_notsure.count() + self.nr_D = self.in_disagreement.count() + self.save() + + +class Motion(models.Model): + """ + Motion instances are put forward to the Advisory Board and Editorial College + and detail suggested changes to rules, procedures etc. + They are meant to be voted on at the annual VGM. + """ + category = models.CharField(max_length=10, choices=MOTION_CATEGORIES, default='General') + VGM = models.ForeignKey(VGM, blank=True, null=True) + background = models.TextField() + motion = models.TextField() + put_forward_by = models.ForeignKey(Contributor) + date = models.DateField() + nr_A = models.PositiveIntegerField(default=0) + in_agreement = models.ManyToManyField(Contributor, + related_name='in_agreement_with_motion', blank=True) + nr_N = models.PositiveIntegerField(default=0) + in_notsure = models.ManyToManyField(Contributor, + related_name='in_notsure_with_motion', blank=True) + nr_D = models.PositiveIntegerField(default=0) + in_disagreement = models.ManyToManyField(Contributor, + related_name='in_disagreement_with_motion', + blank=True) + voting_deadline = models.DateTimeField('voting deadline', default=timezone.now) + accepted = models.NullBooleanField() + + class Meta: + db_table = 'scipost_motion' + + def __str__(self): + return self.motion[:32] + + def as_li(self): + html = ('<div class="Motion" id="motion_id{{ motion_id }}">' + '<h3><em>Motion {{ motion_id }}, put forward by {{ proposer }}</em></h3>' + '<h3>Background:</h3><p>{{ background|linebreaks }}</p>' + '<h3>Motion:</h3>' + '<div class="flex-container"><div class="flex-greybox">' + '<p style="background-color: #eeeeee;">{{ motion|linebreaks }}</p>' + '</div></div>' + '</div>') + context = Context({ + 'motion_id': self.id, + 'proposer': '%s %s' % (self.put_forward_by.user.first_name, + self.put_forward_by.user.last_name), + 'background': self.background, + 'motion': self.motion, }) + template = Template(html) + return template.render(context) + + def votes_as_ul(self): + template = Template(''' + <ul class="opinionsDisplay"> + <li style="background-color: #000099">Agree {{ nr_A }}</li> + <li style="background-color: #555555">Abstain {{ nr_N }}</li> + <li style="background-color: #990000">Disagree {{ nr_D }}</li> + </ul> + ''') + context = Context({'nr_A': self.nr_A, 'nr_N': self.nr_N, 'nr_D': self.nr_D}) + return template.render(context) + + def update_votes(self, contributor_id, vote): + contributor = get_object_or_404(Contributor, pk=contributor_id) + self.in_agreement.remove(contributor) + self.in_notsure.remove(contributor) + self.in_disagreement.remove(contributor) + if vote == 'A': + self.in_agreement.add(contributor) + elif vote == 'N': + self.in_notsure.add(contributor) + elif vote == 'D': + self.in_disagreement.add(contributor) + self.nr_A = self.in_agreement.count() + self.nr_N = self.in_notsure.count() + self.nr_D = self.in_disagreement.count() + self.save() diff --git a/virtualmeetings/templates/virtualmeetings/VGM_detail.html b/virtualmeetings/templates/virtualmeetings/VGM_detail.html new file mode 100644 index 0000000000000000000000000000000000000000..0420363c16e093409a430ceee2b8d259bc6f9dd2 --- /dev/null +++ b/virtualmeetings/templates/virtualmeetings/VGM_detail.html @@ -0,0 +1,352 @@ +{% extends 'scipost/base.html' %} + +{% block pagetitle %}: VGM detail{% endblock pagetitle %} + +{% load staticfiles %} + +{% block bodysup %} + +<script> +$(document).ready(function(){ + + $("#submitFeedbackForm").hide(); + $("#submitFeedbackButton").click( function() { + $(this).next("form").toggle(); + }); + + $("#FellowshipListing").hide(); + $("#FellowshipListingButton").click( function() { + $("#FellowshipListing").toggle(); + }); + + $("#submitNominationForm").hide(); + $("#submitNominationButton").click( function() { + $(this).next("form").toggle(); + }); + + $("#submitMotionForm").hide(); + $("#submitMotionButton").click( function() { + $(this).next("form").toggle(); + }); + + $(".submitRemarkForm").hide(); + + $(".submitRemarkButton").click( function() { + $(this).next("div").toggle(); + }); + }); + +</script> + +<section> + <div class="flex-container"> + <div class="flex-greybox"> + <h1>SciPost Virtual General Meeting</h1> + </div> + </div> + <div class="flex-container"> + <div class="flex-whitebox"> + <h2>On this page:</h2> + <ul> + <li><a href="#Information">Information message</a></li> + <li><a href="#Feedback">Feedback</a></li> + <li><a href="#Nominations">Nominations</a></li> + <li><a href="#Motions">Motions</a></li> + </ul> + </div> + </div> + <hr class="hr12"/> +</section> + + +<section id="Information"> + <div class="flex-container"> + <div class="flex-greybox"> + <h2>Information message from SciPost Administration</h2> + </div> + </div> + <div class="flex-whitebox"> + {{ VGM_information }} + </div> + <br/> + <div class="flex-whitebox"> + <h3>Quick bullet points:</h3> + <ul> + <li>This VGM is scheduled from {{ VGM.start_date|date:'Y-m-d' }} to {{ VGM.end_date|date:'Y-m-d' }}.</li> + <li>Your feedback/suggestions/criticisms on any aspect of SciPost are greatly valued. Provide them by filling the <a href="#FeedbackBox">feedback form</a>.</li> + <li>Your nominations to the Editorial College are welcome. Simply fill the <a href="#NominationBox">nomination form</a>, and cast your vote on current nominations.</li> + <li>For substantial changes, for example to the by-laws, new Motions can be put forward until the end of the meeting using the <a href="#MotionBox">form</a>.</li> + <li>Voting on Motions is open until one week after the meeting.</li> + <li>You a referred to the <a href="{% url 'scipost:EdCol_by-laws' %}">by-laws</a>, section 2 for further details about the procedures.</li> + </ul> + </div> + <br/> + <hr class="hr12"/> +</section> + +<section id="Feedback"> + <div class="flex-container"> + <div class="flex-greybox" id="FeedbackBox"> + <h2>Feedback on SciPost</h2> + <button id="submitFeedbackButton">Provide feedback</button> + <form id="submitFeedbackForm" action="{% url 'virtualmeetings:feedback' VGM_id=VGM.id %}" method="post"> + {% csrf_token %} + {{ feedback_form.as_p }} + <input type="submit" value="Submit"/> + </form> + </div> + </div> + <div class="flex-container"> + <div class="flex-greybox"> + <h2>General Feedback provided</h2> + </div> + </div> + <div class="row"> + <div class="col-1"></div> + <div class="col-10"> + <ul> + {% for feedback in feedback_received %} + <li>{{ feedback.as_li }}</li> + <button class="submitRemarkButton" id="remarkButton{{ nomination.id }}">Add a remark on this Feedback</button> + <div class="submitRemarkForm" id="remarkForm{{ feedback.id }}"> + <form action="{% url 'virtualmeetings:add_remark_on_feedback' VGM_id=VGM.id feedback_id=feedback.id %}" method="post"> + {% csrf_token %} + {{ remark_form.as_p }} + <input type="submit" value="Submit" /> + </form> + </div> + {% if feedback.remark_set.all %} + <h3>Remarks on this feedback:</h3> + <ul> + {% for rem in feedback.remark_set.all %} + {{ rem.as_li }} + {% endfor %} + </ul> + {% endif %} + {% endfor %} + </ul> + </div> + </div> + <hr class="hr12"/> +</section> + +<section id="Nominations"> + <div class="flex-container"> + <div class="flex-greybox" id="NominationBox"> + <h2>Nominations to the Editorial College</h2> + <button id="submitNominationButton">Nominate an Editorial Fellow candidate</button> + <form id="submitNominationForm" action="{% url 'virtualmeetings:nominate_Fellow' VGM_id=VGM.id %}" method="post"> + {% csrf_token %} + {{ nomination_form.as_p }} + <input type="submit" value="Submit"/> + </form> + </div> + </div> + <button id="FellowshipListingButton">View/hide Fellows and Invitations listings</button> + <div class="row" id="FellowshipListing"> + <div class="col-6"> + <div class="flex-container"> + <div class="flex-greybox"> + <h3>Current Fellows</h3> + </div> + </div> + <div class="flex-container"> + <div class="flex-whitebox"> + <table class="tableofInviteesResponded"> + {% for Fellow in current_Fellows %} + <tr><td>{{ Fellow }}</td><td>{{ Fellow.discipline_as_string }}</td> + <td>{{ Fellow.expertises_as_string }}</td></tr> + {% endfor %} + </table> + </div> + </div> + </div> + <div class="col-6"> + <div class="flex-container"> + <div class="flex-greybox"> + <h3>Invitations currently outstanding</h3> + </div> + </div> + <div class="flex-container"> + <div class="flex-whitebox"> + <table class="tableofInvitees"> + {% for invitee in pending_inv_Fellows %} + <tr><td>{{ invitee.first_name }} {{ invitee.last_name }}</td></tr> + {% endfor %} + </table> + </div> + </div> + <div class="flex-container"> + <div class="flex-greybox"> + <h3>Invitations which have been turned down</h3> + </div> + </div> + <div class="flex-container"> + <div class="flex-whitebox"> + <table class="tableofInviteesDeclined"> + {% for invitee in declined_inv_Fellows %} + <tr><td>{{ invitee.first_name }} {{ invitee.last_name }}</td></tr> + {% endfor %} + </table> + </div> + </div> + </div> + </div> + + {% if nominations %} + <div class="row"> + <div class="flex-container"> + <div class="flex-greybox"> + <h2>Nominations under consideration</h2> + </div> + </div> + </div> + <div class="row"> + <div class="col-1"></div> + <div class="col-10"> + <ul style="list-style-type: none;"> + {% for nomination in nominations %} + <li> + {{ nomination.as_li }} + <br/> + <div class="opinionsDisplay"> + <h4>Your opinion on this Nomination (voting deadline: {{ nomination.voting_deadline|date:'y-m-d' }}):</h4> + <form action="{% url 'virtualmeetings:vote_on_nomination' nomination_id=nomination.id vote='A' %}" method="post"> + {% csrf_token %} + <input type="submit" class="agree" value="Agree {{ nomination.nr_A }} "/> + </form> + <form action="{% url 'virtualmeetings:vote_on_nomination' nomination_id=nomination.id vote='N' %}" method="post"> + {% csrf_token %} + <input type="submit" class="notsure" value="Not sure {{ nomination.nr_N }}"/> + </form> + <form action="{% url 'virtualmeetings:vote_on_nomination' nomination_id=nomination.id vote='D'%}" method="post"> + {% csrf_token %} + <input type="submit" class="disagree" value="Disagree {{ nomination.nr_D }}"/> + </form> + {% if request.user.contributor in nomination.in_agreement.all %} + <strong>(you have voted: Agreed)</strong> + {% elif request.user.contributor in nomination.in_notsure.all %} + <strong>(you have voted: Not sure)</strong> + {% elif request.user.contributor in nomination.in_disagreement.all %} + <strong>(you have voted: Disagree)</strong> + {% endif %} + </div> + <br/><br/> + <button class="submitRemarkButton" id="remarkButton{{ nomination.id }}">Add a remark on this Nomination</button> + <div class="submitRemarkForm" id="remarkForm{{ nomination.id }}"> + <form action="{% url 'virtualmeetings:add_remark_on_nomination' VGM_id=VGM.id nomination_id=nomination.id %}" method="post"> + {% csrf_token %} + {{ remark_form.as_p }} + <input type="submit" value="Submit" /> + </form> + </div> + {% if nomination.remark_set.all %} + <h3>Remarks on this nomination:</h3> + <ul> + {% for rem in nomination.remark_set.all %} + {{ rem.as_li }} + {% endfor %} + </ul> + {% endif %} + <hr class="hr6"/> + <br/> + </li> + {% endfor %} + </ul> + </div> + </div> + {% endif %} + + <hr class="hr12"/> + +</section> + +<section id="Motions"> + <div class="flex-container"> + <div class="flex-greybox" id="MotionBox"> + <h2>Submit a new Motion</h2> + <button id="submitMotionButton">Put a new Motion forward</button> + <form id="submitMotionForm" action="{% url 'virtualmeetings:put_motion_forward' VGM_id=VGM.id %}" method="post"> + {% csrf_token %} + {% load crispy_forms_tags %} + {% crispy motion_form %} + </form> + </div> + </div> + + <div class="row"> + <div class="flex-container"> + <div class="flex-greybox"> + <h2>Motions under consideration</h2> + </div> + </div> + </div> + {% for key, val in motion_categories_dict.items %} + <div class="row"> + <div class="col-1"></div> + <div class="flex-container"> + <div class="flex-greybox"> + <h3>{{ val }}:</h3> + </div> + </div> + <div class="col-1"></div> + <div class="col-10"> + <ul> + {% for motion in VGM.motion_set.all %} + {% if motion.category == key %} + <li> + {{ motion.as_li }} + <br/> + <div class="opinionsDisplay"> + <h4>Your opinion on this Motion (voting deadline: {{ motion.voting_deadline|date:'y-m-d' }}):</h4> + <form action="{% url 'virtualmeetings:vote_on_motion' motion_id=motion.id vote='A' %}" method="post"> + {% csrf_token %} + <input type="submit" class="agree" value="Agree {{ motion.nr_A }} "/> + </form> + <form action="{% url 'virtualmeetings:vote_on_motion' motion_id=motion.id vote='N' %}" method="post"> + {% csrf_token %} + <input type="submit" class="notsure" value="Not sure {{ motion.nr_N }}"/> + </form> + <form action="{% url 'virtualmeetings:vote_on_motion' motion_id=motion.id vote='D'%}" method="post"> + {% csrf_token %} + <input type="submit" class="disagree" value="Disagree {{ motion.nr_D }}"/> + </form> + {% if request.user.contributor in motion.in_agreement.all %} + <strong>(you have voted: Agreed)</strong> + {% elif request.user.contributor in motion.in_notsure.all %} + <strong>(you have voted: Not sure)</strong> + {% elif request.user.contributor in motion.in_disagreement.all %} + <strong>(you have voted: Disagree)</strong> + {% endif %} + </div> + <br/><br/> + <button class="submitRemarkButton" id="remarkButton{{ motion.id }}">Add a remark on this Motion</button> + <div class="submitRemarkForm" id="remarkForm{{ motion.id }}"> + <form action="{% url 'virtualmeetings:add_remark_on_motion' motion_id=motion.id %}" method="post"> + {% csrf_token %} + {{ remark_form.as_p }} + <input type="submit" value="Submit" /> + </form> + </div> + {% if motion.remark_set.all %} + <h3>Remarks on this motion:</h3> + <ul> + {% for rem in motion.remark_set.all %} + {{ rem.as_li }} + {% endfor %} + </ul> + {% endif %} + <hr class="hr6"/> + <br/> + </li> + {% endif %} + {% endfor %} + </ul> + </div> + </div> + {% endfor %} + +</section> + + +{% endblock bodysup %} diff --git a/virtualmeetings/templates/virtualmeetings/VGMs.html b/virtualmeetings/templates/virtualmeetings/VGMs.html new file mode 100644 index 0000000000000000000000000000000000000000..a482a21dfadd11a7121e88458dd9bdb2df7ef9a0 --- /dev/null +++ b/virtualmeetings/templates/virtualmeetings/VGMs.html @@ -0,0 +1,26 @@ +{% extends 'scipost/base.html' %} + +{% block pagetitle %}: VGMs{% endblock pagetitle %} + +{% load staticfiles %} + +{% block bodysup %} + + +<section> + <div class="flex-container"> + <div class="flex-greybox"> + <h1>SciPost Virtual General Meetings</h1> + </div> + </div> + + <ul> + {% for VGM in VGM_list %} + <li><a href="{% url 'virtualmeetings:VGM_detail' VGM_id=VGM.id %}">{{ VGM }}</a></li> + {% endfor %} + </ul> + +</section> + + +{% endblock bodysup %} diff --git a/virtualmeetings/tests.py b/virtualmeetings/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6 --- /dev/null +++ b/virtualmeetings/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/virtualmeetings/urls.py b/virtualmeetings/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..556a7eb2b1e4dca34f7330c19ab6779aaac8a080 --- /dev/null +++ b/virtualmeetings/urls.py @@ -0,0 +1,24 @@ + +from django.conf.urls import include, url +from django.views.generic import TemplateView + +from . import views + +urlpatterns = [ + url(r'^$', views.VGMs, name='VGMs'), + url(r'^VGM/(?P<VGM_id>[0-9]+)/$', views.VGM_detail, name='VGM_detail'), + url(r'^feedback/(?P<VGM_id>[0-9]+)$', views.feedback, name='feedback'), + url(r'^add_remark_on_feedback/(?P<VGM_id>[0-9]+)/(?P<feedback_id>[0-9]+)$', + views.add_remark_on_feedback, name='add_remark_on_feedback'), + url(r'^nominate_Fellow/(?P<VGM_id>[0-9]+)$', views.nominate_Fellow, name='nominate_Fellow'), + url(r'^add_remark_on_nomination/(?P<VGM_id>[0-9]+)/(?P<nomination_id>[0-9]+)$', + views.add_remark_on_nomination, name='add_remark_on_nomination'), + url(r'^vote_on_nomination/(?P<nomination_id>[0-9]+)/(?P<vote>[AND])$', + views.vote_on_nomination, name='vote_on_nomination'), + url(r'^put_motion_forward/(?P<VGM_id>[0-9]+)$', + views.put_motion_forward, name='put_motion_forward'), + url(r'^add_remark_on_motion/(?P<motion_id>[0-9]+)$', + views.add_remark_on_motion, name='add_remark_on_motion'), + url(r'^vote_on_motion/(?P<motion_id>[0-9]+)/(?P<vote>[AND])$', + views.vote_on_motion, name='vote_on_motion'), +] diff --git a/virtualmeetings/views.py b/virtualmeetings/views.py new file mode 100644 index 0000000000000000000000000000000000000000..ee357ca3f74aac592483d38e4500a75d1c855bba --- /dev/null +++ b/virtualmeetings/views.py @@ -0,0 +1,252 @@ +import datetime + +from django.contrib.auth.decorators import login_required, permission_required +from django.core.urlresolvers import reverse +from django.http import HttpResponseRedirect +from django.shortcuts import get_object_or_404, render +from django.template import Context, Template +from django.utils import timezone + +from .constants import motion_categories_dict +from .forms import FeedbackForm, NominationForm, MotionForm +from .models import VGM, Feedback, Nomination, Motion + +from scipost.forms import RegistrationInvitation, RemarkForm +from scipost.models import Contributor, Remark + + +@login_required +@permission_required('scipost.can_attend_VGMs') +def VGMs(request): + VGM_list = VGM.objects.all().order_by('start_date') + context = {'VGM_list': VGM_list} + return render(request, 'virtualmeetings/VGMs.html', context) + + +@login_required +@permission_required('scipost.can_attend_VGMs') +def VGM_detail(request, VGM_id): + VGM_instance = get_object_or_404(VGM, id=VGM_id) + VGM_information = Template(VGM_instance.information).render(Context({})) + feedback_received = Feedback.objects.filter(VGM=VGM_instance).order_by('date') + feedback_form = FeedbackForm() + current_Fellows = Contributor.objects.filter( + user__groups__name='Editorial College').order_by('user__last_name') + sent_inv_Fellows = RegistrationInvitation.objects.filter( + invitation_type='F', responded=False) + pending_inv_Fellows = sent_inv_Fellows.filter(declined=False).order_by('last_name') + declined_inv_Fellows = sent_inv_Fellows.filter(declined=True).order_by('last_name') + nomination_form = NominationForm() + nominations = Nomination.objects.filter(accepted=None).order_by('last_name') + motion_form = MotionForm() + remark_form = RemarkForm() + context = { + 'VGM': VGM_instance, + 'VGM_information': VGM_information, + 'feedback_received': feedback_received, + 'feedback_form': feedback_form, + 'current_Fellows': current_Fellows, + 'pending_inv_Fellows': pending_inv_Fellows, + 'declined_inv_Fellows': declined_inv_Fellows, + 'nominations': nominations, + 'nomination_form': nomination_form, + 'motion_categories_dict': motion_categories_dict, + 'motion_form': motion_form, + 'remark_form': remark_form, + } + return render(request, 'virtualmeetings/VGM_detail.html', context) + + +@login_required +@permission_required('scipost.can_attend_VGMs') +def feedback(request, VGM_id=None): + if request.method == 'POST': + feedback_form = FeedbackForm(request.POST) + if feedback_form.is_valid(): + feedback = Feedback(by=request.user.contributor, + date=timezone.now().date(), + feedback=feedback_form.cleaned_data['feedback'],) + if VGM_id: + VGM_instance = get_object_or_404(VGM, id=VGM_id) + feedback.VGM = VGM_instance + feedback.save() + ack_message = 'Your feedback has been received.' + context = {'ack_message': ack_message} + if VGM_id: + context['followup_message'] = 'Return to the ' + context['followup_link'] = reverse('virtualmeetings:VGM_detail', + kwargs={'VGM_id': VGM_id}) + context['followup_link_label'] = 'VGM page' + return render(request, 'scipost/acknowledgement.html', context) + else: + errormessage = 'The form was not filled properly.' + return render(request, 'scipost/error.html', {'errormessage': errormessage}) + else: + errormessage = 'This view can only be posted to.' + return render(request, 'scipost/error.html', {'errormessage': errormessage}) + + +@login_required +@permission_required('scipost.can_attend_VGMs', raise_exception=True) +def add_remark_on_feedback(request, VGM_id, feedback_id): + # contributor = request.user.contributor + feedback = get_object_or_404(Feedback, pk=feedback_id) + if request.method == 'POST': + remark_form = RemarkForm(request.POST) + if remark_form.is_valid(): + remark = Remark(contributor=request.user.contributor, + feedback=feedback, + date=timezone.now(), + remark=remark_form.cleaned_data['remark']) + remark.save() + return HttpResponseRedirect('/VGM/' + str(VGM_id) + + '/#feedback_id' + str(feedback.id)) + else: + errormessage = 'The form was invalidly filled.' + return render(request, 'scipost/error.html', {'errormessage': errormessage}) + else: + errormessage = 'This view can only be posted to.' + return render(request, 'scipost/error.html', {'errormessage': errormessage}) + + +@login_required +@permission_required('scipost.can_attend_VGMs') +def nominate_Fellow(request, VGM_id): + VGM_instance = get_object_or_404(VGM, id=VGM_id) + if request.method == 'POST': + nomination_form = NominationForm(request.POST) + if nomination_form.is_valid(): + nomination = Nomination( + VGM=VGM_instance, + by=request.user.contributor, + date=timezone.now().date(), + first_name=nomination_form.cleaned_data['first_name'], + last_name=nomination_form.cleaned_data['last_name'], + discipline=nomination_form.cleaned_data['discipline'], + expertises=nomination_form.cleaned_data['expertises'], + webpage=nomination_form.cleaned_data['webpage'], + voting_deadline=VGM_instance.end_date + datetime.timedelta(days=7), + ) + nomination.save() + nomination.update_votes(request.user.contributor.id, 'A') + ack_message = 'The nomination has been registered.' + context = {'ack_message': ack_message, + 'followup_message': 'Return to the ', + 'followup_link': reverse('virtualmeetings:VGM_detail', + kwargs={'VGM_id': VGM_id}), + 'followup_link_label': 'VGM page'} + return render(request, 'scipost/acknowledgement.html', context) + else: + errormessage = 'The form was not filled properly.' + return render(request, 'scipost/error.html', {'errormessage': errormessage}) + else: + errormessage = 'This view can only be posted to.' + return render(request, 'scipost/error.html', {'errormessage': errormessage}) + + +@login_required +@permission_required('scipost.can_attend_VGMs', raise_exception=True) +def add_remark_on_nomination(request, VGM_id, nomination_id): + # contributor = request.user.contributor + nomination = get_object_or_404(Nomination, pk=nomination_id) + if request.method == 'POST': + remark_form = RemarkForm(request.POST) + if remark_form.is_valid(): + remark = Remark(contributor=request.user.contributor, + nomination=nomination, + date=timezone.now(), + remark=remark_form.cleaned_data['remark']) + remark.save() + return HttpResponseRedirect('/VGM/' + str(VGM_id) + + '/#nomination_id' + str(nomination.id)) + else: + errormessage = 'The form was invalidly filled.' + return render(request, 'scipost/error.html', {'errormessage': errormessage}) + else: + errormessage = 'This view can only be posted to.' + return render(request, 'scipost/error.html', {'errormessage': errormessage}) + + +@login_required +@permission_required('scipost.can_attend_VGMs', raise_exception=True) +def vote_on_nomination(request, nomination_id, vote): + contributor = request.user.contributor + nomination = get_object_or_404(Nomination, pk=nomination_id) + if timezone.now() > nomination.voting_deadline: + errormessage = 'The voting deadline on this nomination has passed.' + return render(request, 'scipost/error.html', {'errormessage': errormessage}) + nomination.update_votes(contributor.id, vote) + return HttpResponseRedirect('/VGM/' + str(nomination.VGM.id) + + '/#nomination_id' + str(nomination.id)) + + +@login_required +@permission_required('scipost.can_attend_VGMs') +def put_motion_forward(request, VGM_id): + VGM_instance = get_object_or_404(VGM, id=VGM_id) + if timezone.now().date() > VGM_instance.end_date: + errormessage = 'This VGM has ended. No new motions can be put forward.' + return render(request, 'scipost/error.html', {'errormessage': errormessage}) + if request.method == 'POST': + motion_form = MotionForm(request.POST) + if motion_form.is_valid(): + motion = Motion( + category=motion_form.cleaned_data['category'], + VGM=VGM_instance, + background=motion_form.cleaned_data['background'], + motion=motion_form.cleaned_data['motion'], + put_forward_by=request.user.contributor, + date=timezone.now().date(), + voting_deadline=VGM_instance.end_date + datetime.timedelta(days=7), + ) + motion.save() + motion.update_votes(request.user.contributor.id, 'A') + ack_message = 'Your motion has been registered.' + context = {'ack_message': ack_message, + 'followup_message': 'Return to the ', + 'followup_link': reverse('virtualmeetings:VGM_detail', + kwargs={'VGM_id': VGM_id}), + 'followup_link_label': 'VGM page'} + return render(request, 'scipost/acknowledgement.html', context) + else: + errormessage = 'The form was not filled properly.' + return render(request, 'scipost/error.html', {'errormessage': errormessage}) + else: + errormessage = 'This view can only be posted to.' + return render(request, 'scipost/error.html', {'errormessage': errormessage}) + + +@login_required +@permission_required('scipost.can_attend_VGMs', raise_exception=True) +def add_remark_on_motion(request, motion_id): + # contributor = request.user.contributor + motion = get_object_or_404(Motion, pk=motion_id) + if request.method == 'POST': + remark_form = RemarkForm(request.POST) + if remark_form.is_valid(): + remark = Remark(contributor=request.user.contributor, + motion=motion, + date=timezone.now(), + remark=remark_form.cleaned_data['remark']) + remark.save() + return HttpResponseRedirect('/VGM/' + str(motion.VGM.id) + + '/#motion_id' + str(motion.id)) + else: + errormessage = 'The form was invalidly filled.' + return render(request, 'scipost/error.html', {'errormessage': errormessage}) + else: + errormessage = 'This view can only be posted to.' + return render(request, 'scipost/error.html', {'errormessage': errormessage}) + + +@login_required +@permission_required('scipost.can_attend_VGMs', raise_exception=True) +def vote_on_motion(request, motion_id, vote): + contributor = request.user.contributor + motion = get_object_or_404(Motion, pk=motion_id) + if timezone.now() > motion.voting_deadline: + errormessage = 'The voting deadline on this motion has passed.' + return render(request, 'scipost/error.html', {'errormessage': errormessage}) + motion.update_votes(contributor.id, vote) + return HttpResponseRedirect('/VGM/' + str(motion.VGM.id) + + '/#motion_id' + str(motion.id))