Commit 02de6685 authored by cermak's avatar cermak
Browse files

Merge branch 'develop'

parents 0996c5d4 6944eea9
from django.contrib import admin
from django import forms
from .models import Proposals, Instruments, Contacts, Affiliations, Countries, Options, SharedOptions, Samples, SamplePhotos, SampleRemarks, Publications, Experiments, Status, InstrumentGroup, SharedOptionSlot, Report
from django.forms import CharField
from django.conf.urls import url
from django.utils.html import strip_tags
from django.urls import reverse
from django.http import HttpResponseRedirect
from .models import *
import requests
import datetime
class StatusAdminForm(forms.ModelForm):
class Meta:
......@@ -16,7 +23,6 @@ admin.site.register(Status, StatusAdmin)
class ProposalsAdminForm(forms.ModelForm):
class Meta:
model = Proposals
fields = '__all__'
......@@ -24,7 +30,7 @@ class ProposalsAdminForm(forms.ModelForm):
class ProposalsAdmin(admin.ModelAdmin):
form = ProposalsAdminForm
list_display = ['pid', 'name', 'proposer', 'created', 'last_updated', 'grants', 'scientific_bg', 'supervisor']
list_display = ['pid', 'name', 'proposer', 'created', 'last_updated', 'grants', 'scientific_bg', 'supervisor', 'review_process']
readonly_fields = ['slug', 'created', 'last_updated']
admin.site.register(Proposals, ProposalsAdmin)
......@@ -197,19 +203,66 @@ class SampleRemarksAdmin(admin.ModelAdmin):
admin.site.register(SampleRemarks, SampleRemarksAdmin)
class PublicationsAdminForm(forms.ModelForm):
def reload_from_doi(modeladmin, request, queryset):
headersjson = {'Accept': 'application/vnd.citationstyles.csl+json'}
headersaps = {'Accept': 'text/x-bibliography; style=american-physics-society'}
for p in queryset:
data = requests.get(p.link, headers = headersjson)
if data.ok:
data = data.json()
if 'is-referenced-by-count' in data:
p.citations = data['is-referenced-by-count']
if 'container-title' in data:
p.journal = strip_tags(data['container-title'])
if 'issued' in data:
dateparts = data['issued']['date-parts'][0]
if len(dateparts) < 3:
dateparts = dateparts + (3-len(dateparts)) * [1]
p.issued = datetime.datetime(*dateparts[:3])
if 'title' in data:
p.name = strip_tags(data['title'])
data = requests.get(p.link, headers = headersaps)
data.encoding = 'utf-8'
if data.ok and len(data.content) > 4:
p.full_citation = strip_tags(data.text[4:].strip())
p.save()
reload_from_doi.short_description = "Reload selected publications from doi.org"
class PublicationAdminForm(forms.ModelForm):
link = CharField()
class Meta:
model = Publications
fields = '__all__'
model = Publication
fields = ['name']
class PublicationAdmin(admin.ModelAdmin):
form = PublicationAdminForm
list_display = ['created', 'last_updated', 'link', 'issued', 'journal']
readonly_fields = ['created', 'last_updated']
actions = [reload_from_doi]
change_list_template = "admin/publication_list.html"
def get_urls(self):
urls = super().get_urls()
my_urls = [
url(r'^bulk_add/$', self.bulk_add_view, name="bulk_add_publication")
]
return my_urls + urls
class PublicationsAdmin(admin.ModelAdmin):
form = PublicationsAdminForm
list_display = ['created', 'last_updated', 'link', 'year']
readonly_fields = ['created', 'last_updated', 'link', 'year']
def bulk_add_view(self, request):
#check if there are no more links:
links = request.POST.get("links", "")
items = [x for x in links.split('\n') if x and not x.isspace()]
for item in items:
Publication.objects.create(link=item.strip())
admin.site.register(Publications, PublicationsAdmin)
url = reverse('admin:app_publication_changelist')
return HttpResponseRedirect(url)
admin.site.register(Publication, PublicationAdmin)
class ExperimentsAdminForm(forms.ModelForm):
......@@ -237,3 +290,31 @@ class SharedOptionSlotAdmin(admin.ModelAdmin):
admin.site.register(SharedOptionSlot, SharedOptionSlotAdmin)
class UsageInline(admin.TabularInline):
model = Usage
extra = 1
class LogAdminForm(forms.ModelForm):
class Meta:
model = Log
fields = '__all__'
class LogAdmin(admin.ModelAdmin):
form = LogAdminForm
list_display = ['created', 'last_updated', 'proposal', 'instrument','start', 'end', 'duration']
inlines = (UsageInline,)
admin.site.register(Log, LogAdmin)
class ResourceAdminForm(forms.ModelForm):
class Meta:
model = Resource
fields = '__all__'
class ResourceAdmin(admin.ModelAdmin):
form = ResourceAdminForm
list_display = ['name', 'unit']
admin.site.register(Resource, ResourceAdmin)
from . import models
from . import serializers
from rest_framework import viewsets, permissions
class ProposalsViewSet(viewsets.ModelViewSet):
"""ViewSet for the Proposals class"""
queryset = models.Proposals.objects.all()
serializer_class = serializers.ProposalsSerializer
permission_classes = [permissions.IsAuthenticated]
class InstrumentsViewSet(viewsets.ModelViewSet):
"""ViewSet for the Instruments class"""
queryset = models.Instruments.objects.all()
serializer_class = serializers.InstrumentsSerializer
permission_classes = [permissions.IsAuthenticated]
class ContactsViewSet(viewsets.ModelViewSet):
"""ViewSet for the Contacts class"""
queryset = models.Contacts.objects.all()
serializer_class = serializers.ContactsSerializer
permission_classes = [permissions.IsAuthenticated]
class AffiliationsViewSet(viewsets.ModelViewSet):
"""ViewSet for the Affiliations class"""
queryset = models.Affiliations.objects.all()
serializer_class = serializers.AffiliationsSerializer
permission_classes = [permissions.IsAuthenticated]
class CountriesViewSet(viewsets.ModelViewSet):
"""ViewSet for the Countries class"""
queryset = models.Countries.objects.all()
serializer_class = serializers.CountriesSerializer
permission_classes = [permissions.IsAuthenticated]
class OptionsViewSet(viewsets.ModelViewSet):
"""ViewSet for the Options class"""
queryset = models.Options.objects.all()
serializer_class = serializers.OptionsSerializer
permission_classes = [permissions.IsAuthenticated]
class SharedOptionsViewSet(viewsets.ModelViewSet):
"""ViewSet for the SharedOptions class"""
queryset = models.SharedOptions.objects.all()
serializer_class = serializers.SharedOptionsSerializer
permission_classes = [permissions.IsAuthenticated]
class SamplesViewSet(viewsets.ModelViewSet):
"""ViewSet for the Samples class"""
queryset = models.Samples.objects.all()
serializer_class = serializers.SamplesSerializer
permission_classes = [permissions.IsAuthenticated]
class SamplePhotosViewSet(viewsets.ModelViewSet):
"""ViewSet for the SamplePhotos class"""
queryset = models.SamplePhotos.objects.all()
serializer_class = serializers.SamplePhotosSerializer
permission_classes = [permissions.IsAuthenticated]
class SampleRemarksViewSet(viewsets.ModelViewSet):
"""ViewSet for the SampleRemarks class"""
queryset = models.SampleRemarks.objects.all()
serializer_class = serializers.SampleRemarksSerializer
permission_classes = [permissions.IsAuthenticated]
class PublicationsViewSet(viewsets.ModelViewSet):
"""ViewSet for the Publications class"""
queryset = models.Publications.objects.all()
serializer_class = serializers.PublicationsSerializer
permission_classes = [permissions.IsAuthenticated]
class ExperimentsViewSet(viewsets.ModelViewSet):
"""ViewSet for the Experiments class"""
queryset = models.Experiments.objects.all()
serializer_class = serializers.ExperimentsSerializer
permission_classes = [permissions.IsAuthenticated]
......@@ -29,6 +29,7 @@ def create_notice_types(sender, **kwargs):
NoticeType.create("U_submited", "New proposal submitted (UO)", "New proposal was submitted and user office need to check it.")
NoticeType.create("a_submited", "New proposal submitted (A)", "Inform about any new submitted proposal.")
NoticeType.create("a_accepted", "Proposal accepted (A)", "Inform about all proposals accepted by the director.")
NoticeType.create("B_new_proposal", "Board review requested (B)", "MGML Board (you are a member) need to perform review of the proposal.")
#Report
NoticeType.create("X_report", "Experimental report requested", "You need to submit an experimental report. You will always be informed about it.")
# booking notifications
......
......@@ -9,7 +9,7 @@ from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
from .models import Proposals, Instruments, Contacts, Affiliations, Countries, Options, SharedOptions
from .models import Samples, SamplePhotos, SampleRemarks, SharedOptionSlot
from .models import Publications, Experiments, Status, Report
from .models import Publication, Experiments, Status, Report
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit, HTML, Fieldset, Div, ButtonHolder, Field
from dal import autocomplete
......@@ -94,24 +94,63 @@ class StatusForm(forms.ModelForm):
if prop.last_status == 'A': allowed.append('F') # will be finished
if self.user.has_perm('app.change_status'):
showRemark = showHidden = True
if prop.last_status == 'S': allowed.append('U') # will be waiting for panel
if prop.last_status == 'U': allowed.append('P') # will be returned
if prop.last_status == 'U': allowed.append('T') # will be waiting for local contact
if prop.last_status == 'S': allowed.append('U') # takeover
if prop.last_status == 'A': allowed.append('F') # finish accepted
if prop.last_status in 'UTEBWRD': allowed.append('P') # will be returned
if prop.last_status == 'U':
allowed.append('T') # will be waiting for local contact
allowed.append('E') # will be waiting for local contact - board
self.fields['reporter'] = forms.ModelChoiceField(queryset=Contacts.objects.filter(uid__groups__name="board"), required=False)
#if prop.last_status == 'T': allowed.append('P') # go back to preparation
if prop.last_status == 'T':
if prop.proposaltype == 'P':
allowed.append('D') # will be waiting for director
else:
allowed.append('W') # will be waiting for panel
if prop.last_status == 'E':
if prop.proposaltype == 'P':
allowed.append('D') # will be waiting for director
else:
allowed.append('B') # will be waiting for panel
if prop.last_status == 'W': allowed.append('R') # will be in panel
if prop.last_status == 'A': allowed.append('F') # will be finished
if self.user.has_perm('app.approve_technical') and self.user.contact in prop.local_contacts.all():
if prop.last_status == 'T':
allowed.append('T') # stay in technical review
if prop.last_status == 'A': allowed.append('F') # finish accepted
if prop.last_status in 'TE':
allowed.append('P') # will be returned
if prop.proposaltype == 'P':
allowed.append('D') # will be waiting for director
self.info = """This is PROOF-OF-CONCEPT proposal. It will go directly to the director after your review.
Please, fill-in comments if it is elligible for proof-of-concept proposal (short test measurement, clarify potential technical issues) to the 'Technical Check' field, user will not see this - only director. So state clearly if you recommend to accept it, best is to write "ACCEPT/REJECT" at the end.
Also, you can optionally write some comments to the "Remarks to user"
section and user will see it (e.g. to improve next proposals). Proposal will go immediately to director after pressing Submit.
If there is something important missing, you can return it to the user. Then select new status "in preparation" and write remarks to the user.
"""
else:
allowed.append('W') # will be waiting for panel
if prop.last_status == 'E':
allowed.append('E') # stay in technical review
allowed.append('B') # will be waiting for board
else:
allowed.append('T') # stay in technical review
allowed.append('W') # will be waiting for panel
self.info = """Please, fill-in comments (about the feasibility of given proposal from the point of view of instrumentation, technology and expertise of the
team) for the panel/board to the 'Technical Check' field, user will not see this.
Also, you can optionally write some comments to the "Remarks to user"
section and user (and panel) will see it (e.g. to improve next proposals). Proposal will go immediately for the review after pressing Submit.
If there is something important missing, you can return it to the user. Then select new status "in preparation" and write remarks to the user.
If you are not able to review whole proposal, you can keep proposal in technical check (new status "technical check") and just submit your comments. Proposal will stay in review and another local contact needs to finish it. Clarify that with another local contact!
"""
showRemark = showHidden = True
self.fields["hiddenremark"].label = "Technical Check (user won't see)"
if self.user.has_perm('app.approve_board') and prop.last_status == 'B':
allowed.append('D') # will be in panel
allowed.append('P') # will be in preparation
showRemark = showHidden = False
self.info = """Please, fill-in board report and write your decision at the end! You can recommend acceptance/rejection of proposal (new status "by director"),
or you can return it to user (status "in preparation").
The board report has two parts - visible and hidden to the user."""
self.fields["remark"].label = "Board report (user will see)"
self.fields["hiddenremark"].label = "Hidden board report (user won't see)"
showRemark = showHidden = True
if self.user.has_perm('app.takeover_panel') and prop.last_status == 'W':
allowed.append('R') # will be in panel
......@@ -119,10 +158,9 @@ class StatusForm(forms.ModelForm):
if self.user.has_perm('app.takeover_panel') or (self.user.has_perm('app.approve_panel') and prop.reporter.uid == self.user):
if prop.last_status == 'R':
allowed.append('D') # will be by director
allowed.append('X') # will be rejected permanently
allowed.append('P') # will be in preparation
self.info = """Please, fill-in panel report and select your decision. You can accept proposal (new status "by director"),
return it to user (status "in preparation") or you can completelly reject it (status "rejected").
self.info = """Please, fill-in panel report and write your decision at the end! You can recommend acceptance/rejection of proposal (new status "by director"),
or you can return it to user (status "in preparation").
The panel report has two parts - visible and hidden to the user."""
self.fields["remark"].label = "Panel report (user will see)"
self.fields["hiddenremark"].label = "Hidden panel report (user won't see)"
......@@ -131,14 +169,27 @@ The panel report has two parts - visible and hidden to the user."""
if prop.last_status == 'D':
allowed.append('A') # will be accepted
allowed.append('X') # will be rejected permanently
allowed.append('P') # will be in preparation
self.info = "This is the last step in proposal evaluation. It is possible (but not needed) to fill-in remark (visible to user) and optionaly hidden remark (for internal purposes)."
showRemark = showHidden = True
if prop.last_status == 'A':
allowed.append('H') # will be accepted
allowed.append('F') # will be finished
if prop.last_status == 'H':
allowed.append('A') # will be accepted
allowed.append('F') # will be finished
showRemark = showHidden = True
if self.user.has_perm('app.finish_proposal'):
if prop.last_status == 'A':
allowed.append('F') # will be finished
showRemark = True
self.fields["status"].choices = [c for c in self.fields["status"].choices if c[0] in allowed]
def fixStatus(c):
if 'T' in allowed and 'E' in allowed:
if c[0] == 'T': return (c[0], c[1] + " (to panel)")
if c[0] == 'E': return (c[0], c[1] + " (to board)")
return c
self.fields["status"].choices = [fixStatus(c) for c in self.fields["status"].choices if c[0] in allowed]
c = self.fields["status"].choices
self.ConfirmText = "Confirm"
if len(c) == 0: raise Http404
......@@ -150,14 +201,6 @@ The panel report has two parts - visible and hidden to the user."""
self.ConfirmText = "Finish proposal"
self.info = "Do you really want to finish this proposal? It will be archived and you will not be able to book measurements for it anymore."
if c[0][0] == "U": self.ConfirmText = "Takeover proposal"
if c[0][0] == "W":
self.ConfirmText = "Submit technical remarks"
self.fields["hiddenremark"].label = "Technical Review (user won't see)"
self.info = """Please, fill-in comments (about the feasibility of given proposal from the point of view of instrumentation, technology and expertise of the
team) for the panel to the 'Technical Review' field, user will not see this.
Also, you can optionally write some comments to the "Remarks to user"
section and user (and panel) will see it (e.g. to improve next proposals). Proposal will go immediately for the review to the Panel after pressing Submit.
"""
if c[0][0] == "R":
self.ConfirmText = "Takeover proposal"
self.fields['reporter'] = forms.ModelChoiceField(queryset=Contacts.objects.filter(uid__groups__name="panel"))
......@@ -346,6 +389,16 @@ class UserForm(forms.ModelForm):
model = User
fields = ['username', 'email']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_class = 'form-horizontal'
self.helper.form_method = 'post'
self.helper.field_class = 'col-sm-10'
self.helper.label_class = 'col-sm-2'
self.helper.add_input(Submit('submit', 'Save'))
def clean_email(self):
email = self.cleaned_data.get('email')
username = self.cleaned_data.get('username')
......@@ -403,10 +456,10 @@ class SampleRemarksForm(forms.ModelForm):
fields = ['remark', 'sample', 'creator']
class PublicationsForm(forms.ModelForm):
class PublicationForm(forms.ModelForm):
class Meta:
model = Publications
fields = ['link', 'year', 'authors']
model = Publication
fields = ['link', 'issued', 'authors']
class DateRangeField(Fieldset):
......
# Generated by Django 3.0.4 on 2020-03-18 14:24
import datetime
from django.db import migrations, models
def forward(apps, schema_editor):
Proposals = apps.get_model("app", "Proposals")
for proposal in Proposals.objects.filter(proposaltype__in = 'SL'):
proposal.review_process = 'P'
proposal.save()
class Migration(migrations.Migration):
dependencies = [
('app', '0045_report'),
]
operations = [
migrations.AddField(
model_name='proposals',
name='review_process',
field=models.CharField(choices=[('P', 'panel'), ('B', 'board'), ('N', 'none')], default='N', max_length=1),
),
migrations.AlterField(
model_name='proposals',
name='last_status',
field=models.CharField(choices=[('P', 'in preparation'), ('S', 'submitted'), ('U', 'user office checking'), ('T', 'technical check'), ('E', 'technical check'), ('B', 'board review'), ('W', 'waiting for panel'), ('R', 'in panel review'), ('D', 'by director'), ('A', 'accepted'), ('H', 'on hold'), ('F', 'finished'), ('X', 'rejected')], default='P', max_length=1),
),
migrations.AlterField(
model_name='report',
name='deadline',
field=models.DateTimeField(default=datetime.datetime(2020, 5, 17, 14, 24, 4, 659877)),
),
migrations.AlterField(
model_name='status',
name='status',
field=models.CharField(choices=[('P', 'in preparation'), ('S', 'submitted'), ('U', 'user office checking'), ('T', 'technical check'), ('E', 'technical check'), ('B', 'board review'), ('W', 'waiting for panel'), ('R', 'in panel review'), ('D', 'by director'), ('A', 'accepted'), ('H', 'on hold'), ('F', 'finished'), ('X', 'rejected')], max_length=1),
),
migrations.RunPython(forward),
]
# Generated by Django 3.0.4 on 2020-03-18 14:39
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('app', '0046_auto_20200318_1424'),
]
operations = [
migrations.AlterField(
model_name='proposals',
name='review_process',
field=models.CharField(choices=[('P', 'panel'), ('B', 'board'), ('N', 'none')], default='N', editable=False, max_length=1),
),
migrations.AlterField(
model_name='report',
name='deadline',
field=models.DateTimeField(default=datetime.datetime(2020, 5, 17, 14, 39, 9, 376737)),
),
]
# Generated by Django 3.0.4 on 2020-03-18 14:45
import app.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('app', '0047_auto_20200318_1439'),
]
operations = [
migrations.AlterField(
model_name='report',
name='deadline',
field=models.DateTimeField(default=app.models.default_report_time),
),
migrations.AlterField(
model_name='report',
name='year',
field=models.PositiveIntegerField(default=app.models.current_year),
),
]
# Generated by Django 3.0.4 on 2020-03-18 23:21
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('app', '0048_auto_20200318_1445'),
]
operations = [
migrations.AlterModelOptions(
name='proposals',
options={'ordering': ('-created',), 'permissions': (('change_status', 'Can set proposal to any status, edit proposal type anytime'), ('approve_technical', 'Can submit technical comments'), ('takeover_panel', 'Can assign a reviewer and submit any review'), ('approve_panel', 'Can submit panel decision'), ('approve_board', 'Can submit board decision'), ('view_panel_proposals', 'Can view panel related proposals'), ('view_board_proposals', 'Can view board related proposals'), ('approve_director', 'Can submit director approval'), ('finish_proposal', 'Can finish approved proposal'))},
),
]
# Generated by Django 3.0.4 on 2020-03-18 23:27
from django.db import migrations
from django.contrib.auth.models import Group, Permission
def add_group_permissions(apps, schema_editor):
# Criando Administrador
group, created = Group.objects.get_or_create(name='board')
if created:
permissions_qs = Permission.objects.filter(
codename__in=['view_board_proposals',
'approve_board',
'see_hidden_remarks',]
)
group.permissions.set(permissions_qs)
group.save()
class Migration(migrations.Migration):
dependencies = [
('app', '0049_auto_20200318_2321'),
]
operations = [
migrations.RunPython(add_group_permissions),
]
# Generated by Django 3.0.6 on 2020-05-07 12:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('app', '0050_auto_20200318_2327'),
]
operations = [
migrations.CreateModel(
name='Publication',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)),
('last_updated', models.DateTimeField(auto_now=True)),
('link', models.URLField(blank=True, max_length=300, null=True)),
('name', models.CharField(blank=True, max_length=1000, null=True)),
('journal', models.CharField(blank=True, db_index=True, max_length=255, null=True)),
('citations', models.IntegerField(null=True)),
('issued', models.DateField(db_index=True, null=True)),
('full_citation', models.CharField(blank=True, max_length=2000, null=True)),
('authors', models.ManyToManyField(to='app.Contacts')),
],
options={
'ordering': ('-created',),
},
),
migrations.AlterField(
model_name='proposals',
name='publications',
field=models.ManyToManyField(blank=True, to='app.Publication'),
),
migrations.DeleteModel(
name='Publications',
),
]
# Generated by Django 3.0.6 on 2020-06-09 08:42
from django.db import migrations, models
import django.db.models.deletion