diff --git a/README.md b/README.md index 4964856aaf986dfcde25c82e5ea719ff786481bc..840f8eb6cf8a0ee49903a730e08ce4489d9b70dc 100644 --- a/README.md +++ b/README.md @@ -89,13 +89,13 @@ All modules are configured in the `.bootstraprc` file. Most modules are disabled In order to collect static files from all `INSTALLED_APPS`, i.e. the assets managed by Webpack, run: ```shell -(scipostenv) $ ./manage collectstatic +(scipostenv) $ ./manage.py collectstatic ``` This will put all static files in the `STATIC_ROOT` folder defined in your settings file. It's a good idea to use the clear option in order to remove stale static files: ```shell -(scipostenv) $ ./manage collectstatic --clear +(scipostenv) $ ./manage.py collectstatic --clear ``` ### Create and run migrations diff --git a/SciPost_v1/settings/base.py b/SciPost_v1/settings/base.py index ec132d3dd4453c2710cf181d9fbda302e98005c3..7758de6ee883377b474cd745ca936f0a56bade95 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' ) @@ -154,7 +156,6 @@ TEMPLATES = [ 'django.template.context_processors.media', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', - 'scipost.context_processors.searchform', ], }, }, diff --git a/SciPost_v1/settings/local_geert.py b/SciPost_v1/settings/local_geert.py new file mode 100644 index 0000000000000000000000000000000000000000..4a4066579f22049ce0159958c327d3cf11c97124 --- /dev/null +++ b/SciPost_v1/settings/local_geert.py @@ -0,0 +1,11 @@ +from .base import * +import os + +# THE MAIN THING HERE +DEBUG = True + +# Static and media +STATIC_ROOT = os.path.join(BASE_DIR, 'local_files', 'static') +MEDIA_ROOT = os.path.join(BASE_DIR, 'local_files', 'media') +WEBPACK_LOADER['DEFAULT']['BUNDLE_DIR_NAME'] =\ + os.path.join(BASE_DIR, 'static', 'bundles') diff --git a/SciPost_v1/settings/local_jorran.py b/SciPost_v1/settings/local_jorran.py index 1eef068e824bdaaa5e51f1b16a15cf80cee2b724..12dcdcfa68b6054fae82ccda1936723bab419563 100644 --- a/SciPost_v1/settings/local_jorran.py +++ b/SciPost_v1/settings/local_jorran.py @@ -3,6 +3,15 @@ from .base import * # THE MAIN THING HERE DEBUG = True +# Debug toolbar settings +INSTALLED_APPS += ( + 'debug_toolbar', +) +MIDDLEWARE_CLASSES += ( + 'debug_toolbar.middleware.DebugToolbarMiddleware', +) +INTERNAL_IPS = ['127.0.0.1', '::1'] + # Static and media STATIC_ROOT = '/Users/jorranwit/Develop/SciPost/scipost_v1/local_files/static/' MEDIA_ROOT = '/Users/jorranwit/Develop/SciPost/scipost_v1/local_files/media/' diff --git a/SciPost_v1/urls.py b/SciPost_v1/urls.py index 9b85d34e8e3967fdfc82a646d9d8c76443eb3c9a..11c640116c6bffe41466958b01638c47d83103c9 100644 --- a/SciPost_v1/urls.py +++ b/SciPost_v1/urls.py @@ -31,5 +31,11 @@ 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")), ] + +if settings.DEBUG: + import debug_toolbar + urlpatterns += [ + url(r'^__debug__/', include(debug_toolbar.urls)), + ] diff --git a/commentaries/views.py b/commentaries/views.py index 640100a7c22436864ce3aab1e7b49e76f10a0c67..e103063d5ff6074d4a37aa0137a51efb166697cd 100644 --- a/commentaries/views.py +++ b/commentaries/views.py @@ -282,7 +282,9 @@ class CommentaryListView(ListView): context = super().get_context_data(**kwargs) # Get newest comments - context['comment_list'] = Comment.objects.vetted().order_by('-date_submitted')[:10] + context['comment_list'] = Comment.objects.vetted().select_related( + 'author__user', 'submission', 'commentary').order_by( + '-date_submitted')[:10] # Form into the context! context['form'] = self.form 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/requirements.txt b/requirements.txt index 4e1cfb752935824e29985016edb1fd1bbd4c30e8..a5406b89e2df28e0fe9829ed55487fbc1455a010 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ Babel==2.3.4 Django==1.10.3 django-countries==4.0 django-crispy-forms==1.6.1 +django-debug-toolbar==1.7 django-extensions==1.7.6 django-filter==1.0.0 django-guardian==1.4.6 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/context_processors.py b/scipost/context_processors.py deleted file mode 100644 index 19c0fbf686bb62fb3d3908d1f0a370bc9f23165b..0000000000000000000000000000000000000000 --- a/scipost/context_processors.py +++ /dev/null @@ -1,5 +0,0 @@ -from .forms import SearchForm - - -def searchform(request): - return {'search_form': SearchForm()} 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..b1fb349c3b77db8289ca530b3eb26ea3b02409e3 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 @@ -210,9 +210,7 @@ class RemarkForm(forms.Form): class SearchForm(forms.Form): - query = forms.CharField(max_length=100, label='', - widget=forms.TextInput(attrs={ - 'class': 'form-control mr-0 mb-2 mr-lg-2 mb-lg-0'})) + q = forms.CharField(max_length=100) class EmailGroupMembersForm(forms.Form): 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..119cd4ca97bba8bcb05694673499dc7ba9965a07 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,271 +428,6 @@ 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() - - ######### # Lists # ######### diff --git a/scipost/templates/scipost/about.html b/scipost/templates/scipost/about.html index 110047ecb983230817065416eb8de8739ca60712..03d7103478b3c093637a4c33bcfb65cda022698a 100644 --- a/scipost/templates/scipost/about.html +++ b/scipost/templates/scipost/about.html @@ -154,26 +154,34 @@ <li>Prof. <a href="https://www.uni-tuebingen.de/en/faculties/faculty-of-science/departments/physics/institutes/institute-for-theoretical-physics/research-groups/andergassen-group.html">Sabine Andergassen</a><br/>(Tübingen)</li> <li>Prof. <a href="http://www.physik.uni-wuerzburg.de/~assaad/">Fakher Assaad</a><br/>(Würzbrug)</li> <li>Dr <a href="http://www.attaccalite.com">Claudio Attaccalite</a><br/>(Marseille)</li> + <li>Prof. <a href="https://denis114.wordpress.com">Denis Bartolo</a><br/>(ENS Lyon)</li> <li>Prof. <a href="http://www.physik.unizh.ch/~lbaudis/index.html">Laura Baudis</a><br/>(Zurich)</li> <li>Prof. <a href="http://www.lorentz.leidenuniv.nl/beenakker/">Carlo Beenakker</a><br/>(Leiden)</li> + <li>Prof. <a href="https://www.coulomb.univ-montp2.fr/perso/ludovic.berthier/">Ludovic Berthier</a><br/>(Montpellier)</li> <li>Prof. <a href="http://ipht.cea.fr/Pisp/giulio.biroli/index_en.php">Giulio Biroli</a><br/>(CEA Saclay)</li> <li>Prof. <a href="http://www.en.physik.uni-muenchen.de/personen/professoren/bloch/index.html">Immanuel Bloch</a><br/>(LMU Munich)</li> <li>Prof. <a href="https://staff.fnwi.uva.nl/j.deboer/">Jan de Boer</a><br/>(U. van Amsterdam)</li> <li>Prof. <a href="http://www.uva.nl/en/about-the-uva/organisation/staff-members/content/b/o/d.bonn/d.bonn.html">Daniel Bonn</a><br/>(U. van Amsterdam)</li> <li>Prof. <a href="http://www.statphys.sissa.it/wordpress/?page_id=1731">Pasquale Calabrese</a><br/>(SISSA)</li> + <li>Prof. <a href="http://personalpages.to.infn.it/~caselle/index_en.html">Michele Caselle</a><br/>(Torino)</li> <li>Prof. <a href="http://www.saha.ac.in/cmp/bikask.chakrabarti/bikas.html">Bikas Chakrabarti</a><br/>(Kolkata)</li> <li>Prof. <a href="http://www.tcm.phy.cam.ac.uk/~nrc25/">Nigel Cooper</a><br/>(Cambridge)</li> </ul> </div> <div class="flex-whitebox"> <ul> + <li>Prof. <a href="http://physics.cornell.edu/csaba-csaki">Csaba Csaki</a><br/>(Cornell)</li> <li>Prof. <a href="http://theory.tifr.res.in/~kedar/">Kedar Damle</a><br/>(TIFR Mumbai)</li> <li>Prof. <a href="http://researchers.uq.edu.au/researcher/1134">Matthew Davis</a><br/>(U. of Queensland)</li> <li>Prof. <a href="http://www-thphys.physics.ox.ac.uk/people/FabianEssler/">Fabian Essler</a><br/>(U. of Oxford)</li> + <li>Prof. <a href="http://www.pd.infn.it/~feruglio/">Ferruccio Feruglio</a><br/>(Padova, INFN)</li> <li>Prof. <a href="http://www.ms.unimelb.edu.au/~jdgier@unimelb/">Jan de Gier</a><br/>(U. of Melbourne)</li> <li>Prof. <a href="http://www.desy.de/about_desy/leading_scientists/beate_heinemann/index_eng.html">Beate Heinemann</a><br/>(DESY; Freiburg)</li> <li>Prof. <a href="http://katzgraber.org">Helmut Katzgraber</a><br/>(Texas A&M)</li> + <li>Prof. <a href="https://web.physik.rwth-aachen.de/~mkraemer/">Michael Krämer</a><br/>(RWTH Aachen)</li> <li>Prof. <a href="https://www.pmmh.espci.fr/~jorge/">Jorge Kurchan</a><br/>(<a href="https://www.pmmh.espci.fr/?-Home-">PMMH</a> Paris, CNRS)</li> + <li>Prof. <a href="https://vivo.brown.edu/display/glandsbe">Greg Landsberg</a><br/>(Brown Univ.)</li> + <li>Prof. <a href="https://www.mpg.de/6812374/chem_physik_fester_stoffe_mackenzie">Andrew P. MacKenzie</a><br/>(MPICPS Dresden, St-Andrews)</li> <li>Prof. <a href="http://www.ens-lyon.fr/PHYSIQUE/presentation/annuaire/maillet-jean-michel">Jean Michel Maillet</a><br/>(ENS Lyon)</li> <li>Prof. <a href="https://www.mpg.de/343435/physik_komplexer_systeme_wissM28">Roderich Moessner</a><br/>(MPIPKS Dresden)</li> <li>Prof. <a href="https://www.uibk.ac.at/exphys/ultracold/people/christoph.naegerl/">Hanns-Christoph Nägerl</a><br/>(Innsbruck)</li> @@ -184,17 +192,21 @@ <ul> <li>Prof. <a href="https://staff.fnwi.uva.nl/b.nienhuis/">Bernard Nienhuis</a><br/>(U. van Amsterdam)</li> <!--<li>Prof. <a href="http://www.kip.uni-heidelberg.de/people/index.php?num=552">Markus Oberthaler</a><br/>(U. Heidelberg)</li>--> + <li>Prof. <a href="http://www.lpthe.jussieu.fr/~pioline/">Boris Pioline</a><br/>(LPTHE Jussieu)</li> <li>Prof. <a href="https://www.uu.nl/staff/RHHGvanRoij/0">René van Roij</a><br/>(Utrecht)</li> <li>Prof. <a href="http://www.thp.uni-koeln.de/rosch">Achim Rosch</a><br/>(U. of Cologne)</li> <li>Prof. <a href="http://saleur.sandvox.net">Hubert Saleur</a><br/>(CEA Saclay/USC)</li> <li>Prof. <a href="http://www.phy.ohiou.edu/people/faculty/sandler.html">Nancy Sandler</a><br/>(Ohio)</li> + <li>Dr. <a href="http://www.sussex.ac.uk/profiles/320359">Veronica Sanz</a><br/>(Sussex)</li> <li>Prof. <a href="http://www-thphys.physics.ox.ac.uk/people/SubirSarkar/">Subir Sarkar</a><br/>(Oxford; Niels Bohr Institute)</li> <li>Prof. <a href="https://staff.fnwi.uva.nl/c.j.m.schoutens/">Kareljan Schoutens</a><br/>(U. van Amsterdam)</li> + <li>Dr <a href="http://www.phys.ens.fr/~guilhem/">Guilhem Semerjian</a><br/>(ENS Paris)</li> <li>Prof. <a href="http://www-thphys.physics.ox.ac.uk/people/SteveSimon/">Steve Simon</a><br/>(U. of Oxford)</li> <li>Prof. <a href="http://bec.science.unitn.it/infm-bec/people/stringari.html">Sandro Stringari</a><br/>(Trento)</li> <li>Prof. <a href="http://www.damtp.cam.ac.uk/user/tong/">David Tong</a><br/>(Cambridge)</li> <li>Prof. <a href="http://www.physique.usherbrooke.ca/pages/en/node/3412">André-Marie Tremblay</a><br/>(Sherbrooke)</li> <li>Prof. <a href="http://trivediresearch.org.ohio-state.edu">Nandini Trivedi</a><br/>(Ohio State U.)</li> + <li>Prof. <a href="http://vergassolalab.ucsd.edu">Massimo Vergassola</a><br/>(UC Sand Diego)</li> </ul> </div> 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/templates/scipost/navbar.html b/scipost/templates/scipost/navbar.html index 04cce9fedff90383eb8df8bf20fd99d4fdc1d979..29161549ed69fbbefbb3dd886868c413b44f1f65 100644 --- a/scipost/templates/scipost/navbar.html +++ b/scipost/templates/scipost/navbar.html @@ -38,9 +38,8 @@ {% endif %} </ul> - <form action="{% url 'scipost:search' %}" method="post" class="form-inline"> - {% csrf_token %} - {{ search_form }} + <form action="{% url 'scipost:search' %}" method="get" class="form-inline"> + <input class="form-control mr-0 mb-2 mr-lg-2 mb-lg-0" id="id_q" maxlength="100" name="q" type="text" required=""> <button class="btn btn-primary" type="submit">Search</button> </form> </div> diff --git a/scipost/templates/scipost/personal_page.html b/scipost/templates/scipost/personal_page.html index 151c11eb9e901a98ddaf2ab62aa65d523e5e31aa..2506e6f5f49f8fddd69695fc4e08fee6b2a39079 100644 --- a/scipost/templates/scipost/personal_page.html +++ b/scipost/templates/scipost/personal_page.html @@ -87,7 +87,7 @@ {% block content %} -{% if not request.user|is_in_group:'Registered Contributors' %} +{% if 'Registered Contributors' not in user_groups %} <div class="row"> <div class="col-12"> <hr class="hr12"> @@ -110,7 +110,7 @@ <div class="col-12"> <ul class="personalTabMenu"> <li><a class="TabItem" id="AccountTab">Account</a></li> - {% if request.user|is_in_group:'Editorial Administrators' or request.user|is_in_group:'Advisory Board' or request.user|is_in_group:'Editorial College' or request.user|is_in_group:'Vetting Editors' or request.user|is_in_group:'Ambassadors' or request.user|is_in_group:'Junior Ambassadors' %} + {% if 'Editorial Administrators' not in user_groups or 'Advisory Board' not in user_groups or 'Editorial College' not in user_groups or 'Vetting Editors' not in user_groups or 'Ambassadors' not in user_groups or 'Junior Ambassadors' not in user_groups %} <li><a class="TabItem" id="EdActionTab">Editorial Actions</a></li> {% endif %} <li><a class="TabItem" id="RefereeingTab">Refereeing</a></li> @@ -119,13 +119,13 @@ <li><a class="TabItem" id="ThesesTab">Theses</a></li> <li><a class="TabItem" id="CommentsTab">Comments</a></li> <li><a class="TabItem" id="AuthorRepliesTab">Author Replies</a></li> - {% if request.user|is_in_group:'Testers' %} + {% if 'Testers' in user_groups %} <li><a class="TabItem" id="ListsTab">Lists</a></li> {% endif %} - {% if request.user|is_in_group:'Testers' %} + {% if 'Testers' in user_groups %} <li><a class="TabItem" id="TeamsTab">Teams</a></li> {% endif %} - {% if request.user|is_in_group:'Testers' %} + {% if 'Testers' in user_groups %} <li><a class="TabItem" id="GraphsTab">Graphs</a></li> {% endif %} </ul> @@ -160,31 +160,31 @@ {% endif %} </div> <div class="col-md-6"> - {% if request.user|is_in_group:'SciPost Administrators' %} + {% if 'SciPost Administrators' in user_groups %} <h3>You are a SciPost Administrator.</h3> {% endif %} - {% if request.user|is_in_group:'Editorial Administrators' %} + {% if 'Editorial Administrators' in user_groups %} <h3>You are a SciPost Editorial Administrator.</h3> {% endif %} - {% if request.user|is_in_group:'Advisory Board' %} + {% if 'Advisory Board' in user_groups %} <h3>You are a member of the Advisory Board.</h3> {% endif %} - {% if request.user|is_in_group:'Editorial College' %} + {% if 'Editorial College' in user_groups %} <h3>You are a member of the Editorial College.</h3> {% endif %} - {% if request.user|is_in_group:'Vetting Editors' %} + {% if 'Vetting Editors' in user_groups %} <h3>You are a SciPost Vetting Editor.</h3> {% endif %} - {% if request.user|is_in_group:'Registered Contributors' %} + {% if 'Registered Contributors' in user_groups %} <h3>You are a Registered Contributor.</h3> {% endif %} - {% if request.user|is_in_group:'Testers' %} + {% if 'Testers' in user_groups %} <h3>You are a SciPost Tester.</h3> {% endif %} - {% if request.user|is_in_group:'Ambassadors' %} + {% if 'Ambassadors' in user_groups %} <h3>You are a SciPost Ambassador.</h3> {% endif %} - {% if request.user|is_in_group:'Junior Ambassadors' %} + {% if 'Junior Ambassadors' in user_groups %} <h3>You are a SciPost Junior Ambassador.</h3> {% endif %} @@ -239,7 +239,7 @@ </div> -{% if request.user|is_in_group:'SciPost Administrators' or request.user|is_in_group:'Editorial Administrators' or request.user|is_in_group:'Editorial College' or request.user|is_in_group:'Vetting Editors' or request.user|is_in_group:'Ambassadors' or request.user|is_in_group:'Junior Ambassadors' %} +{% if 'SciPost Administrators' in user_groups or 'Editorial Administrators' in user_groups or 'Editorial College' in user_groups or 'Vetting Editors' in user_groups or 'Ambassadors' in user_groups or 'Junior Ambassadors' in user_groups %} <div class="TabSection" id="EdActions"> <div class="row"> @@ -251,7 +251,7 @@ </div> <div class="row"> - {% if request.user|is_in_group:'SciPost Administrators' or request.user|is_in_group:'Advisory Board' or request.user|is_in_group:'Ambassadors' or request.user|is_in_group:'Junior Ambassadors' %} + {% if 'SciPost Administrators' in user_groups or 'Advisory Board' in user_groups or 'Ambassadors' in user_groups or 'Junior Ambassadors' in user_groups %} <div class="col-md-4"> <h3>Registration actions</h3> <ul> @@ -315,7 +315,7 @@ {% endif %} </div> - {% if request.user|is_in_group:'Editorial Administrators' or request.user|is_in_group:'Editorial College' %} + {% if 'Editorial Administrators' in user_groups or 'Editorial College' in user_groups %} <div class="col-md-4"> <h3>Info</h3> <ul> @@ -380,7 +380,7 @@ {% for task in pending_ref_tasks %} <li>{{ task.submission }}, due {{ task.submission.reporting_deadline }}. <a href="{% url 'submissions:submit_report' arxiv_identifier_w_vn_nr=task.submission.arxiv_identifier_w_vn_nr %}">Submit your Report</a> - <a href="{% url 'submissions:communication' arxiv_identifier_w_vn_nr=task.submission.arxiv_identifier_w_vn_nr comtype='RtoE' referee_id=request.user.contributor.id %}">Write to the Editor-in-charge</a>. + <a href="{% url 'submissions:communication' arxiv_identifier_w_vn_nr=task.submission.arxiv_identifier_w_vn_nr comtype='RtoE' referee_id=contributor.user.contributor.id %}">Write to the Editor-in-charge</a>. </li> {% endfor %} </ul> @@ -411,7 +411,7 @@ <ul class="mt-3"> {% for sub in own_submissions %} {{ sub.header_as_li_for_authors }} - {% if request.user.contributor == sub.submitted_by %} + {% if contributor.user.contributor == sub.submitted_by %} <p><a href="{% url 'submissions:communication' arxiv_identifier_w_vn_nr=sub.arxiv_identifier_w_vn_nr comtype='AtoE' %}">Write to the Editor-in-charge</a>.</p> {% endif %} {% endfor %} diff --git a/scipost/templates/scipost/search.html b/scipost/templates/scipost/search.html index 050dbafd542fae2959975c7f27117d6c7cece837..911e4686ed965a1f8a4ebb054ac3e25b8a0adcaa 100644 --- a/scipost/templates/scipost/search.html +++ b/scipost/templates/scipost/search.html @@ -2,125 +2,171 @@ {% block pagetitle %}: list{% endblock pagetitle %} -{% block headsup %} - -{% endblock headsup %} - -{% block bodysup %} - - -<section> - <h1>Search results</h1> - - {% if publication_search_list or commentary_search_list or submission_search_list or thesislink_search_list or comment_search_link %} - {% else %} - <p>Your search query did not return any result.</p> - {% endif %} - - {% if publication_search_list %} - <br /> - <hr class="hr12"> - <h3>Publications:</h3> - - <ul> - {% for publication in publication_search_list %} - {{ publication.header_as_li }} - {% endfor %} - </ul> - - <div class="pagination"> - <span class="step-links"> - {% if publication_search_list.has_previous %} - <a href="?publication_search_list_page={{ publication_search_list.previous_page_number }}">previous</a> - {% endif %} - <span class="current"> - Page {{ publication_search_list.number }} of {{ publication_search_list.paginator.num_pages }}. - </span> - {% if publication_search_list.has_next %} - <a href="?publication_search_list_page={{ publication_search_list.next_page_number }}">next</a> - {% endif %} - </span> - </div> - - {% endif %} - - - {% if commentary_search_list %} - <br /> - <hr class="hr12"> - <h3>Commentaries:</h3> - - <ul> - {% for commentary in commentary_search_list %} - {{ commentary.header_as_li }} - {% endfor %} - </ul> - - <div class="pagination"> - <span class="step-links"> - {% if commentary_search_list.has_previous %} - <a href="?commentary_search_list_page={{ commentary_search_list.previous_page_number }}">previous</a> - {% endif %} - <span class="current"> - Page {{ commentary_search_list.number }} of {{ commentary_search_list.paginator.num_pages }}. - </span> - {% if commentary_search_list.has_next %} - <a href="?commentary_search_list_page={{ commentary_search_list.next_page_number }}">next</a> - {% endif %} - </span> - </div> - - {% endif %} - - - {% if submission_search_list %} - <br /> - <hr class="hr12"> - <h3>Submissions:</h3> - <ul> - {% for submission in submission_search_list %} - {{ submission.header_as_li }} - {% endfor %} - </ul> - - <div class="pagination"> - <span class="step-links"> - {% if submission_search_list.has_previous %} - <a href="?submission_search_list_page={{ submission_search_list.previous_page_number }}">previous</a> - {% endif %} - <span class="current"> - Page {{ submission_search_list.number }} of {{ submission_search_list.paginator.num_pages }} - </span> - {% if submission_search_list.has_next %} - <a href="?submission_search_list_page={{ submission_search_list.next_page_number }}">next</a> - {% endif %} - </span> - </div> - - {% endif %} - - {% if thesislink_search_list %} - <br /> - <hr class="hr12"> - <h3>Theses:</h3> - <ul> - {% for thesislink in thesislink_search_list %} - {% include 'theses/_thesislink_header_as_li.html' with thesislink=thesislink %} - {% endfor %} - </ul> - {% endif %} - - {% if comment_search_list %} - <br /> - <hr class="hr12"> - <h3>Comments:</h3> - <ul> - {% for comment in comment_search_list %} - {{ comment.header_as_li }} - {% endfor %} - </ul> - {% endif %} - -</section> - -{% endblock bodysup %} +{% block content %} + +<div class="row"> + <div class="col-12"> + <h1>Search results{% if search_term %}: <small><i>{{ search_term }}</i></small>{% endif %}</h1> + {% if not publication_search_list and not commentary_search_list and not submission_search_list and not thesislink_search_list and not comment_search_link and not comment_search_list %} + <p>Your search query did not return any result.</p> + {% endif %} + </div> +</div> + +{% if publication_search_list %} +<hr> +<div class="row"> + <div class="col-12"> + <div class="panel"> + <h2>Publications</h2> + </div> + </div> +</div> + +<div class="row"> + <div class="col-12"> + {% for publication in publication_search_list %} + {{ publication.header_as_li }} + {% endfor %} + </div> +</div> + +<div class="row"> + <div class="col-12"> + <div class="pagination"> + <span class="step-links"> + {% if publication_search_list.has_previous %} + <a href="?publication_search_list_page={{ publication_search_list.previous_page_number }}">previous</a> + {% endif %} + <span class="current"> + Page {{ publication_search_list.number }} of {{ publication_search_list.paginator.num_pages }}. + </span> + {% if publication_search_list.has_next %} + <a href="?publication_search_list_page={{ publication_search_list.next_page_number }}">next</a> + {% endif %} + </span> + </div> + + </div> +</div> +{% endif %} + + +{% if commentary_search_list %} +<hr> +<div class="row"> + <div class="col-12"> + <div class="panel"> + <h2>Commentaries</h2> + </div> + </div> +</div> +<div class="row"> + <div class="col-12"> + <ul> + {% for commentary in commentary_search_list %} + {{ commentary.header_as_li }} + {% endfor %} + </ul> + </div> +</div> + +<div class="row"> + <div class="col-12"> + <div class="pagination"> + <span class="step-links"> + {% if commentary_search_list.has_previous %} + <a href="?commentary_search_list_page={{ commentary_search_list.previous_page_number }}">previous</a> + {% endif %} + <span class="current"> + Page {{ commentary_search_list.number }} of {{ commentary_search_list.paginator.num_pages }}. + </span> + {% if commentary_search_list.has_next %} + <a href="?commentary_search_list_page={{ commentary_search_list.next_page_number }}">next</a> + {% endif %} + </span> + </div> + + </div> +</div> +{% endif %} + + +{% if submission_search_list %} +<hr> +<div class="row"> + <div class="col-12"> + <div class="panel"> + <h2>Submissions</h2> + </div> + </div> +</div> +<div class="row"> + <div class="col-12"> + <ul> + {% for submission in submission_search_list %} + {{ submission.header_as_li }} + {% endfor %} + </ul> + </div> +</div> + +<div class="row"> + <div class="col-12"> + <div class="pagination"> + <span class="step-links"> + {% if submission_search_list.has_previous %} + <a href="?submission_search_list_page={{ submission_search_list.previous_page_number }}">previous</a> + {% endif %} + <span class="current"> + Page {{ submission_search_list.number }} of {{ submission_search_list.paginator.num_pages }} + </span> + {% if submission_search_list.has_next %} + <a href="?submission_search_list_page={{ submission_search_list.next_page_number }}">next</a> + {% endif %} + </span> + </div> + </div> +</div> +{% endif %} + +{% if thesislink_search_list %} +<hr> +<div class="row"> + <div class="col-12"> + <div class="panel"> + <h2>Theses</h2> + </div> + </div> +</div> +<div class="row"> + <div class="col-12"> + <ul> + {% for thesislink in thesislink_search_list %} + {% include 'theses/_thesislink_header_as_li.html' with thesislink=thesislink %} + {% endfor %} + </ul> + {% endif %} + + {% if comment_search_list %} + <div class="row"> + <div class="col-12"> + <div class="panel"> + <h2>Comments</h2> + </div> + </div> + </div> + <div class="row"> + <div class="col-12"> + <ul> + {% for comment in comment_search_list %} + {{ comment.header_as_li }} + {% endfor %} + </ul> + </div> + </div> + {% endif %} + </div> +</div> + +{% endblock content %} diff --git a/scipost/templatetags/scipost_extras.py b/scipost/templatetags/scipost_extras.py index 3a34f87e76bc8559d8943d360f43469a14860f95..40863a5f2d987024c06caeb3c480f2477293a174 100644 --- a/scipost/templatetags/scipost_extras.py +++ b/scipost/templatetags/scipost_extras.py @@ -21,8 +21,7 @@ def sort_by(queryset, order): @register.filter(name='is_in_group') def is_in_group(user, group_name): - group = Group.objects.get(name=group_name) - return True if group in user.groups.all() else False + return user.groups.filter(name=group_name).exists() @register.filter(name='associated_contributors') 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 940eb79e17a271404a4146d50f13cd427e761907..295f62b52a930e54af13098bbcc530adb54e488e 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 ############## @@ -151,17 +153,15 @@ def documentsSearchResults(query): def search(request): """ For the global search form in navbar """ - if request.method == 'POST': - form = SearchForm(request.POST) - if form.is_valid(): - context = documentsSearchResults(form.cleaned_data['query']) - request.session['query'] = form.cleaned_data['query'] - else: - context = {} + form = SearchForm(request.GET or None) + context = {} + if form.is_valid(): + context = documentsSearchResults(form.cleaned_data['q']) + request.session['query'] = form.cleaned_data['q'] + context['search_term'] = form.cleaned_data['q'] elif 'query' in request.session: - context = documentsSearchResults(request.session['query']) - else: - context = {} + context = documentsSearchResults(request.session['query']) + context['search_term'] = request.session['query'] if 'publication_search_queryset' in context: publication_search_list_paginator = Paginator(context['publication_search_queryset'], 10) @@ -232,12 +232,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) @@ -941,137 +935,135 @@ def mark_unavailable_period(request): return redirect('scipost:personal_page') +@login_required def personal_page(request): """ The Personal Page is the main view for accessing user functions. """ - if request.user.is_authenticated(): - contributor = Contributor.objects.get(user=request.user) - - # Compile the unavailability periods: - now = timezone.now() - unavailabilities = UnavailabilityPeriod.objects.filter( - contributor=contributor).exclude(end__lt=now).order_by('start') - unavailability_form = UnavailabilityPeriodForm() - - # if an editor, count the number of actions required: - nr_reg_to_vet = 0 - nr_reg_awaiting_validation = 0 - nr_submissions_to_assign = 0 - nr_recommendations_to_prepare_for_voting = 0 - if is_SP_Admin(request.user): - intwodays = now + timezone.timedelta(days=2) - - # count the number of pending registration requests - nr_reg_to_vet = Contributor.objects.filter(user__is_active=True, status=0).count() - nr_reg_awaiting_validation = Contributor.objects.filter( - user__is_active=False, key_expires__gte=now, - key_expires__lte=intwodays, status=0).count() - nr_submissions_to_assign = Submission.objects.filter(status__in=['unassigned']).count() - nr_recommendations_to_prepare_for_voting = EICRecommendation.objects.filter( - submission__status__in=['voting_in_preparation']).count() - nr_assignments_to_consider = 0 - active_assignments = None - nr_reports_to_vet = 0 - if is_MEC(request.user): - nr_assignments_to_consider = (EditorialAssignment.objects - .filter(to=contributor, accepted=None, deprecated=False) + contributor = Contributor.objects.select_related('user').get(user=request.user) + user_groups = contributor.user.groups.values_list('name', flat=True) + + # Compile the unavailability periods: + now = timezone.now() + unavailabilities = UnavailabilityPeriod.objects.filter( + contributor=contributor).exclude(end__lt=now).order_by('start') + unavailability_form = UnavailabilityPeriodForm() + + # if an editor, count the number of actions required: + nr_reg_to_vet = 0 + nr_reg_awaiting_validation = 0 + nr_submissions_to_assign = 0 + nr_recommendations_to_prepare_for_voting = 0 + if is_SP_Admin(contributor.user): + intwodays = now + timezone.timedelta(days=2) + + # count the number of pending registration requests + nr_reg_to_vet = Contributor.objects.filter(user__is_active=True, status=0).count() + nr_reg_awaiting_validation = Contributor.objects.filter( + user__is_active=False, key_expires__gte=now, + key_expires__lte=intwodays, status=0).count() + nr_submissions_to_assign = Submission.objects.filter(status__in=['unassigned']).count() + nr_recommendations_to_prepare_for_voting = EICRecommendation.objects.filter( + submission__status__in=['voting_in_preparation']).count() + nr_assignments_to_consider = 0 + active_assignments = None + nr_reports_to_vet = 0 + if is_MEC(contributor.user): + nr_assignments_to_consider = (EditorialAssignment.objects + .filter(to=contributor, accepted=None, deprecated=False) + .count()) + active_assignments = EditorialAssignment.objects.filter( + to=contributor, accepted=True, completed=False) + nr_reports_to_vet = Report.objects.filter( + status=0, submission__editor_in_charge=contributor).count() + nr_commentary_page_requests_to_vet = 0 + nr_comments_to_vet = 0 + nr_thesislink_requests_to_vet = 0 + nr_authorship_claims_to_vet = 0 + if is_VE(request.user): + nr_commentary_page_requests_to_vet = Commentary.objects.filter(vetted=False).count() + nr_comments_to_vet = Comment.objects.filter(status=0).count() + nr_thesislink_requests_to_vet = ThesisLink.objects.filter(vetted=False).count() + nr_authorship_claims_to_vet = AuthorshipClaim.objects.filter(status='0').count() + nr_ref_inv_to_consider = RefereeInvitation.objects.filter( + referee=contributor, accepted=None, cancelled=False).count() + pending_ref_tasks = RefereeInvitation.objects.filter( + referee=contributor, accepted=True, fulfilled=False) + # Verify if there exist objects authored by this contributor, + # whose authorship hasn't been claimed yet + own_submissions = (Submission.objects + .filter(authors__in=[contributor], is_current=True) + .order_by('-submission_date')) + own_commentaries = (Commentary.objects + .filter(authors__in=[contributor]) + .order_by('-latest_activity')) + own_thesislinks = ThesisLink.objects.filter(author_as_cont__in=[contributor]) + nr_submission_authorships_to_claim = (Submission.objects.filter( + author_list__contains=contributor.user.last_name) + .exclude(authors__in=[contributor]) + .exclude(authors_claims__in=[contributor]) + .exclude(authors_false_claims__in=[contributor]) .count()) - active_assignments = EditorialAssignment.objects.filter( - to=contributor, accepted=True, completed=False) - nr_reports_to_vet = Report.objects.filter( - status=0, submission__editor_in_charge=contributor).count() - nr_commentary_page_requests_to_vet = 0 - nr_comments_to_vet = 0 - nr_thesislink_requests_to_vet = 0 - nr_authorship_claims_to_vet = 0 - if is_VE(request.user): - nr_commentary_page_requests_to_vet = Commentary.objects.filter(vetted=False).count() - nr_comments_to_vet = Comment.objects.filter(status=0).count() - nr_thesislink_requests_to_vet = ThesisLink.objects.filter(vetted=False).count() - nr_authorship_claims_to_vet = AuthorshipClaim.objects.filter(status='0').count() - nr_ref_inv_to_consider = RefereeInvitation.objects.filter( - referee=contributor, accepted=None, cancelled=False).count() - pending_ref_tasks = RefereeInvitation.objects.filter( - referee=contributor, accepted=True, fulfilled=False) - # Verify if there exist objects authored by this contributor, - # whose authorship hasn't been claimed yet - own_submissions = (Submission.objects - .filter(authors__in=[contributor], is_current=True) - .order_by('-submission_date')) - own_commentaries = (Commentary.objects - .filter(authors__in=[contributor]) - .order_by('-latest_activity')) - own_thesislinks = ThesisLink.objects.filter(author_as_cont__in=[contributor]) - nr_submission_authorships_to_claim = (Submission.objects.filter( - author_list__contains=contributor.user.last_name) - .exclude(authors__in=[contributor]) - .exclude(authors_claims__in=[contributor]) - .exclude(authors_false_claims__in=[contributor]) - .count()) - nr_commentary_authorships_to_claim = (Commentary.objects.filter( - author_list__contains=contributor.user.last_name) - .exclude(authors__in=[contributor]) - .exclude(authors_claims__in=[contributor]) - .exclude(authors_false_claims__in=[contributor]) - .count()) - nr_thesis_authorships_to_claim = (ThesisLink.objects.filter( - author__contains=contributor.user.last_name) - .exclude(author_as_cont__in=[contributor]) - .exclude(author_claims__in=[contributor]) - .exclude(author_false_claims__in=[contributor]) + nr_commentary_authorships_to_claim = (Commentary.objects.filter( + author_list__contains=contributor.user.last_name) + .exclude(authors__in=[contributor]) + .exclude(authors_claims__in=[contributor]) + .exclude(authors_false_claims__in=[contributor]) .count()) - own_comments = (Comment.objects - .filter(author=contributor, is_author_reply=False) - .order_by('-date_submitted')) - own_authorreplies = (Comment.objects - .filter(author=contributor, is_author_reply=True) - .order_by('-date_submitted')) - lists_owned = List.objects.filter(owner=contributor) - lists = List.objects.filter(teams_with_access__members__in=[contributor]) - teams_led = Team.objects.filter(leader=contributor) - teams = Team.objects.filter(members__in=[contributor]) - graphs_owned = Graph.objects.filter(owner=contributor) - graphs_private = Graph.objects.filter(Q(teams_with_access__leader=contributor) - | Q(teams_with_access__members__in=[contributor])) - appellation = title_dict[contributor.title] + ' ' + contributor.user.last_name - context = { - 'contributor': contributor, - 'appellation': appellation, - 'unavailabilities': unavailabilities, - 'unavailability_form': unavailability_form, - 'nr_reg_to_vet': nr_reg_to_vet, - 'nr_reg_awaiting_validation': nr_reg_awaiting_validation, - 'nr_commentary_page_requests_to_vet': nr_commentary_page_requests_to_vet, - 'nr_comments_to_vet': nr_comments_to_vet, - 'nr_thesislink_requests_to_vet': nr_thesislink_requests_to_vet, - 'nr_authorship_claims_to_vet': nr_authorship_claims_to_vet, - 'nr_reports_to_vet': nr_reports_to_vet, - 'nr_submissions_to_assign': nr_submissions_to_assign, - 'nr_recommendations_to_prepare_for_voting': nr_recommendations_to_prepare_for_voting, - 'nr_assignments_to_consider': nr_assignments_to_consider, - 'active_assignments': active_assignments, - 'nr_submission_authorships_to_claim': nr_submission_authorships_to_claim, - 'nr_commentary_authorships_to_claim': nr_commentary_authorships_to_claim, - 'nr_thesis_authorships_to_claim': nr_thesis_authorships_to_claim, - 'nr_ref_inv_to_consider': nr_ref_inv_to_consider, - 'pending_ref_tasks': pending_ref_tasks, - 'own_submissions': own_submissions, - 'own_commentaries': own_commentaries, - 'own_thesislinks': own_thesislinks, - 'own_comments': own_comments, 'own_authorreplies': own_authorreplies, - 'lists_owned': lists_owned, - 'lists': lists, - 'teams_led': teams_led, - 'teams': teams, - 'graphs_owned': graphs_owned, - 'graphs_private': graphs_private, - } - return render(request, 'scipost/personal_page.html', context) - else: - form = AuthenticationForm() - context = {'form': form} - return render(request, 'scipost/login.html', context) + nr_thesis_authorships_to_claim = (ThesisLink.objects.filter( + author__contains=contributor.user.last_name) + .exclude(author_as_cont__in=[contributor]) + .exclude(author_claims__in=[contributor]) + .exclude(author_false_claims__in=[contributor]) + .count()) + own_comments = (Comment.objects.select_related('author', 'submission') + .filter(author=contributor, is_author_reply=False) + .order_by('-date_submitted')) + own_authorreplies = (Comment.objects + .filter(author=contributor, is_author_reply=True) + .order_by('-date_submitted')) + lists_owned = List.objects.filter(owner=contributor) + lists = List.objects.filter(teams_with_access__members__in=[contributor]) + teams_led = Team.objects.select_related('leader__user').filter(leader=contributor) + teams = Team.objects.select_related('leader__user').filter(members__in=[contributor]) + graphs_owned = Graph.objects.filter(owner=contributor) + graphs_private = Graph.objects.filter(Q(teams_with_access__leader=contributor) + | Q(teams_with_access__members__in=[contributor])) + appellation = title_dict[contributor.title] + ' ' + contributor.user.last_name + context = { + 'contributor': contributor, + 'user_groups': user_groups, + 'appellation': appellation, + 'unavailabilities': unavailabilities, + 'unavailability_form': unavailability_form, + 'nr_reg_to_vet': nr_reg_to_vet, + 'nr_reg_awaiting_validation': nr_reg_awaiting_validation, + 'nr_commentary_page_requests_to_vet': nr_commentary_page_requests_to_vet, + 'nr_comments_to_vet': nr_comments_to_vet, + 'nr_thesislink_requests_to_vet': nr_thesislink_requests_to_vet, + 'nr_authorship_claims_to_vet': nr_authorship_claims_to_vet, + 'nr_reports_to_vet': nr_reports_to_vet, + 'nr_submissions_to_assign': nr_submissions_to_assign, + 'nr_recommendations_to_prepare_for_voting': nr_recommendations_to_prepare_for_voting, + 'nr_assignments_to_consider': nr_assignments_to_consider, + 'active_assignments': active_assignments, + 'nr_submission_authorships_to_claim': nr_submission_authorships_to_claim, + 'nr_commentary_authorships_to_claim': nr_commentary_authorships_to_claim, + 'nr_thesis_authorships_to_claim': nr_thesis_authorships_to_claim, + 'nr_ref_inv_to_consider': nr_ref_inv_to_consider, + 'pending_ref_tasks': pending_ref_tasks, + 'own_submissions': own_submissions, + 'own_commentaries': own_commentaries, + 'own_thesislinks': own_thesislinks, + 'own_comments': own_comments, 'own_authorreplies': own_authorreplies, + 'lists_owned': lists_owned, + 'lists': lists, + 'teams_led': teams_led, + 'teams': teams, + 'graphs_owned': graphs_owned, + 'graphs_private': graphs_private, + } + return render(request, 'scipost/personal_page.html', context) @login_required diff --git a/submissions/models.py b/submissions/models.py index 8ca56af4a8d1d553d10b49041712a5eee45a5a53..7aea9adab2567057eb7c7753c025fa62f2e26c4b 100644 --- a/submissions/models.py +++ b/submissions/models.py @@ -634,7 +634,7 @@ class Report(models.Model): # -3: rejected (not useful) # -4: rejected (not academic in style) status = models.SmallIntegerField(default=0) - submission = models.ForeignKey(Submission, on_delete=models.CASCADE) + submission = models.ForeignKey(Submission, related_name='reports', on_delete=models.CASCADE) vetted_by = models.ForeignKey(Contributor, related_name="report_vetted_by", blank=True, null=True, on_delete=models.CASCADE) # `invited' filled from RefereeInvitation objects at moment of report submission diff --git a/submissions/templatetags/submissions_extras.py b/submissions/templatetags/submissions_extras.py index 7460a3c30745b62cf6f7ce4f29aff1f7bc5ab83b..b2b785c0357b7be546be77fe2a3eb0d1761022da 100644 --- a/submissions/templatetags/submissions_extras.py +++ b/submissions/templatetags/submissions_extras.py @@ -73,7 +73,7 @@ def required_actions(submission): todo.append('The refereeing deadline has passed. Please either extend it, ' 'or formulate your Editorial Recommendation if at least ' 'one Report has been received.') - reports = submission.report_set.all() + reports = submission.reports.all() for report in reports: if report.status == 0: todo.append('The Report from %s has been delivered but is not yet vetted. ' diff --git a/submissions/views.py b/submissions/views.py index 2681da5f9dfafd7d11f42ee3bb6f18a085af8dc5..553e344673c0f4b4ddcf79b1c25bf4d19b2c8cc3 100644 --- a/submissions/views.py +++ b/submissions/views.py @@ -271,7 +271,10 @@ def submission_detail_wo_vn_nr(request, arxiv_identifier_wo_vn_nr): def submission_detail(request, arxiv_identifier_w_vn_nr): - submission = get_object_or_404(Submission, arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) + submission = get_object_or_404(Submission.objects.select_related( + 'editor_in_charge', 'publication__in_issue__in_volume__in_journal' + ).prefetch_related('authors'), + arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) try: is_author = request.user.contributor in submission.authors.all() except AttributeError: @@ -288,7 +291,7 @@ def submission_detail(request, arxiv_identifier_w_vn_nr): form = CommentForm() - reports = submission.report_set.all() + reports = submission.reports.prefetch_related('reports') try: author_replies = Comment.objects.filter(submission=submission, is_author_reply=True, 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))