SciPost Code Repository

Skip to content
Snippets Groups Projects
Commit ba850d94 authored by Jorran de Wit's avatar Jorran de Wit
Browse files

Extend Partner functionalities

- Edit Institutions
- Edit Partner + Contacts related
- Add Contact
parent fb2af5fb
No related branches found
No related tags found
No related merge requests found
...@@ -14,6 +14,140 @@ from .models import Partner, ProspectivePartner, ProspectiveContact, Prospective ...@@ -14,6 +14,140 @@ from .models import Partner, ProspectivePartner, ProspectiveContact, Prospective
from scipost.models import TITLE_CHOICES from scipost.models import TITLE_CHOICES
class InstitutionForm(forms.ModelForm):
class Meta:
model = Institution
fields = (
'kind',
'name',
'acronym',
'address',
'country'
)
class PartnerForm(forms.ModelForm):
class Meta:
model = Partner
fields = (
'institution',
'status',
'main_contact'
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['main_contact'].queryset = self.instance.contact_set.all()
class ContactForm(forms.ModelForm):
"""
This Contact form is mainly used for editing Contact instances.
"""
class Meta:
model = Contact
fields = (
'kind',
)
class NewContactForm(ContactForm):
"""
This Contact form is used to create new Contact instances, as it will also handle
possible sending and activation of User instances coming with the new Contact.
"""
title = forms.ChoiceField(choices=TITLE_CHOICES, label='Title')
first_name = forms.CharField()
last_name = forms.CharField()
email = forms.CharField()
existing_user = None
def __init__(self, *args, **kwargs):
"""
Partner is a required argument to tell the formset which Partner the Contact
is being edited for in the current form.
"""
self.partner = kwargs.pop('partner')
super().__init__(*args, **kwargs)
def clean_email(self):
"""
Check if User already is known in the system.
"""
email = self.cleaned_data['email']
try:
self.existing_user = User.objects.get(email=email)
if not self.data.get('confirm_use_existing', '') == 'on':
# Do not give error if user wants to use existing User
self.add_error('email', 'This User is already registered.')
self.fields['confirm_use_existing'] = forms.BooleanField(
required=False, initial=False, label='Use the existing user instead: %s %s'
% (self.existing_user.first_name,
self.existing_user.last_name))
except User.DoesNotExist:
pass
return email
def save(self, commit=True):
"""
If existing user is found, add it to the Partner.
"""
if self.existing_user and self.data.get('confirm_use_existing', '') == 'on':
# Do not create new Contact
try:
# Link Contact to new Partner
contact = self.existing_user.partner_contact
contact.partners.add(self.partner)
# TODO: Send mail to contact informing him/her about the new Partner
except Contact.DoesNotExist:
# Not yet a 'Contact-User'
contact = super().save(commit=False)
contact.title = self.existing_user.contributor.title
contact.user = self.existing_user
contact.save()
contact.partners.add(self.partner)
# TODO: Send mail to contact informing him/her about the new Partner
return contact
# Create complete new Account (User + Contact)
user = User(
first_name=self.cleaned_data['first_name'],
last_name=self.cleaned_data['last_name'],
email=self.cleaned_data['email'],
username=self.cleaned_data['email'],
is_active=False,
)
user.save()
contact = Contact(
user=user,
title=self.cleaned_data['title'],
kind=self.cleaned_data['kind']
)
contact.save()
contact.partners.add(self.partner)
# TODO: Send mail to contact to let him/her activate account
return contact
class ContactFormset(forms.BaseModelFormSet):
"""
Use custom formset to make sure the delete action will not delete an entire Contact
if the Contact still has relations with other Partners.
"""
def __init__(self, *args, **kwargs):
"""
Partner is a required argument to tell the formset which Partner the Contact
is being edited for in the current form.
"""
self.partner = kwargs.pop('partner')
super().__init__(*args, **kwargs)
def delete_existing(self, obj, commit=True):
'''Deletes an existing model instance.'''
if commit:
obj.delete_or_remove_partner(self.partner)
class PromoteToPartnerForm(forms.ModelForm): class PromoteToPartnerForm(forms.ModelForm):
address = forms.CharField(widget=forms.Textarea(), required=False) address = forms.CharField(widget=forms.Textarea(), required=False)
acronym = forms.CharField() acronym = forms.CharField()
......
...@@ -130,6 +130,16 @@ class Contact(models.Model): ...@@ -130,6 +130,16 @@ class Contact(models.Model):
def __str__(self): def __str__(self):
return '%s %s, %s' % (self.get_title_display(), self.user.last_name, self.user.first_name) return '%s %s, %s' % (self.get_title_display(), self.user.last_name, self.user.first_name)
def delete_or_remove_partner(self, partner, *args, **kwargs):
"""
Custom `delete` method as the contact does not always need to be deleted,
but sometimes just the link with a specific partner needs to be removed.
"""
self.partners.remove(partner)
if self.partners.exists():
return self
return super().delete(*args, **kwargs)
@property @property
def kind_display(self): def kind_display(self):
""" """
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
<ul> <ul>
{% for contact in partner.contact_set.all %} {% for contact in partner.contact_set.all %}
<li> <li>
<h4>{{ contact.get_title_display }} {{ contact.user.first_name }} {{ contact.user.last_name }}</h4> <h4>{{ contact.get_title_display }} {{ contact.user.first_name }} {{ contact.user.last_name }} {% if not contact.user.is_active %}<span class="label label-sm label-warning">Inactive</span>{% endif %}</h4>
<div>({{ contact.kind_display }})</div> <div>({{ contact.kind_display }})</div>
<div class="mb-2"><a href="mailto:{{ contact.user.email }}">{{ contact.user.email }}</a></div> <div class="mb-2"><a href="mailto:{{ contact.user.email }}">{{ contact.user.email }}</a></div>
</li> </li>
...@@ -28,7 +28,13 @@ ...@@ -28,7 +28,13 @@
</ul> </ul>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<p>Edit</p> <h3>Actions</h3>
<ul>
<li><a href="{% url 'partners:partner_edit' partner.id %}">Edit Partner</a></li>
<li><a href="{% url 'partners:institution_edit' partner.institution.id %}">Edit Institution</a></li>
<li><a href="{% url 'partners:partner_add_contact' partner.id %}">Add Contact</a></li>
</ul>
</div> </div>
</div> </div>
</div> </div>
...@@ -59,8 +59,8 @@ ...@@ -59,8 +59,8 @@
<div class="col-md-6"> <div class="col-md-6">
<h3>Partners</h3> <h3>Partners</h3>
<ul> <ul>
{% for partner in request.user.partners.all %} {% for partner in request.user.partner_contact.partners.all %}
<li>{{partner}}</li> <li>{{partner.institution}}</li>
{% empty %} {% empty %}
<li>No partners found. Please contact the SciPost admin.</li> <li>No partners found. Please contact the SciPost admin.</li>
{% endfor %} {% endfor %}
......
{% extends 'partners/_partners_page_base.html' %}
{% block breadcrumb_items %}
{{block.super}}
<span class="breadcrumb-item">Edit Institution</span>
{% endblock %}
{% block pagetitle %}{{block.super}} Edit Institution{% endblock pagetitle %}
{% load bootstrap %}
{% block content %}
<div class="row">
<div class="col-12">
<h1 class="highlight">Edit Institution {{institution}}</h1>
</div>
</div>
<div class="row">
<div class="col-12">
<form method="post">
{% csrf_token %}
<div class="mb-5">
{{ form|bootstrap }}
</div>
<input class="btn btn-primary" type="submit" value="Submit"/>
</form>
</div>
</div>
{% endblock content %}
{% extends 'partners/_partners_page_base.html' %}
{% block breadcrumb_items %}
{{block.super}}
<span class="breadcrumb-item">Add Contact</span>
{% endblock %}
{% block pagetitle %}{{block.super}} Add Contact{% endblock pagetitle %}
{% load bootstrap %}
{% block content %}
<div class="row">
<div class="col-12">
<h1 class="highlight">Add Contact for Partner {{partner}}</h1>
</div>
</div>
<div class="row">
<div class="col-12">
<form method="post">
{% csrf_token %}
<div class="mb-5">
{{ form|bootstrap }}
</div>
<input class="btn btn-primary" type="submit" value="Submit"/>
</form>
</div>
</div>
{% endblock content %}
{% extends 'partners/_partners_page_base.html' %}
{% block breadcrumb_items %}
{{block.super}}
<span class="breadcrumb-item">Edit Partner</span>
{% endblock %}
{% block pagetitle %}{{block.super}} Edit Partner{% endblock pagetitle %}
{% load bootstrap %}
{% block content %}
<div class="row">
<div class="col-12">
<h1 class="highlight">Edit Partner</h1>
</div>
</div>
<div class="row">
<div class="col-12">
<form method="post">
{% csrf_token %}
<div class="mb-5">
{{ form|bootstrap }}
</div>
<h2>Contacts</h2>
{{ contact_formset.management_form }}
{% for form in contact_formset %}
<div class="contact-form-group">
<h3>{{form.instance}}</h3>
<p>{{ form.instance.user.email }}</p>
<div class="mb-3">{{ form|bootstrap }}</div>
</div>
{% endfor %}
<input class="btn btn-primary" type="submit" value="Submit"/>
</form>
</div>
</div>
{% endblock content %}
{% block footer_script %}
<script>
function delete_hide_contact_groups(delete_input) {
input_el = $(delete_input);
if( input_el.prop('checked') ) {
input_el
.parents('.contact-form-group')
.addClass('delete-form-group');
} else {
input_el
.parents('.contact-form-group')
.removeClass('delete-form-group');
}
}
$('.contact-form-group [name$="DELETE"]').on('change click', function() {
delete_hide_contact_groups(this);
});
</script>
{% endblock %}
...@@ -6,14 +6,25 @@ urlpatterns = [ ...@@ -6,14 +6,25 @@ urlpatterns = [
url(r'^$', views.supporting_partners, name='partners'), url(r'^$', views.supporting_partners, name='partners'),
url(r'^dashboard$', views.dashboard, name='dashboard'), url(r'^dashboard$', views.dashboard, name='dashboard'),
url(r'^membership_request$', views.membership_request, name='membership_request'), url(r'^membership_request$', views.membership_request, name='membership_request'),
url(r'^prospect_partners/add$', views.add_prospective_partner,
# Prospects
url(r'^prospects/add$', views.add_prospective_partner,
name='add_prospective_partner'), name='add_prospective_partner'),
url(r'^prospect_partners/contacts/(?P<contact_id>[0-9]+)/email$', url(r'^prospects/contacts/(?P<contact_id>[0-9]+)/email$',
views.email_prospartner_contact, name='email_prospartner_contact'), views.email_prospartner_contact, name='email_prospartner_contact'),
url(r'^prospect_partner/(?P<prospartner_id>[0-9]+)/contacts/add$', url(r'^prospects/(?P<prospartner_id>[0-9]+)/contacts/add$',
views.add_prospartner_contact, name='add_prospartner_contact'), views.add_prospartner_contact, name='add_prospartner_contact'),
url(r'^prospect_partner/(?P<prospartner_id>[0-9]+)/promote$', url(r'^prospects/(?P<prospartner_id>[0-9]+)/promote$',
views.promote_prospartner, name='promote_prospartner'), views.promote_prospartner, name='promote_prospartner'),
url(r'^prospect_partner/(?P<prospartner_id>[0-9]+)/events/add$', url(r'^prospects/(?P<prospartner_id>[0-9]+)/events/add$',
views.add_prospartner_event, name='add_prospartner_event'), views.add_prospartner_event, name='add_prospartner_event'),
# Institutions
url(r'institutions/(?P<institution_id>[0-9]+)/edit$', views.institution_edit,
name='institution_edit'),
# Partners
url(r'(?P<partner_id>[0-9]+)/edit$', views.partner_edit, name='partner_edit'),
url(r'(?P<partner_id>[0-9]+)/contacts/add$', views.partner_add_contact,
name='partner_add_contact'),
] ]
from django.contrib import messages from django.contrib import messages
from django.db import transaction from django.db import transaction
from django.forms import modelformset_factory, formset_factory from django.forms import modelformset_factory
from django.shortcuts import get_object_or_404, render, reverse, redirect from django.shortcuts import get_object_or_404, render, reverse, redirect
from django.utils import timezone from django.utils import timezone
...@@ -10,10 +10,12 @@ from .constants import PROSPECTIVE_PARTNER_REQUESTED,\ ...@@ -10,10 +10,12 @@ from .constants import PROSPECTIVE_PARTNER_REQUESTED,\
PROSPECTIVE_PARTNER_APPROACHED, PROSPECTIVE_PARTNER_ADDED,\ PROSPECTIVE_PARTNER_APPROACHED, PROSPECTIVE_PARTNER_ADDED,\
PROSPECTIVE_PARTNER_EVENT_REQUESTED, PROSPECTIVE_PARTNER_EVENT_EMAIL_SENT PROSPECTIVE_PARTNER_EVENT_REQUESTED, PROSPECTIVE_PARTNER_EVENT_EMAIL_SENT
from .models import Partner, ProspectivePartner, ProspectiveContact,\ from .models import Partner, ProspectivePartner, ProspectiveContact,\
ProspectivePartnerEvent, MembershipAgreement ProspectivePartnerEvent, MembershipAgreement, Contact, Institution
from .forms import ProspectivePartnerForm, ProspectiveContactForm,\ from .forms import ProspectivePartnerForm, ProspectiveContactForm,\
EmailProspectivePartnerContactForm, PromoteToPartnerForm,\ EmailProspectivePartnerContactForm, PromoteToPartnerForm,\
ProspectivePartnerEventForm, MembershipQueryForm, PromoteToContactForm, PromoteToContactFormset ProspectivePartnerEventForm, MembershipQueryForm, PromoteToContactForm,\
PromoteToContactFormset, PartnerForm, ContactForm, ContactFormset,\
NewContactForm, InstitutionForm
from .utils import PartnerUtils from .utils import PartnerUtils
...@@ -100,6 +102,69 @@ def promote_prospartner(request, prospartner_id): ...@@ -100,6 +102,69 @@ def promote_prospartner(request, prospartner_id):
return render(request, 'partners/promote_prospartner.html', context) return render(request, 'partners/promote_prospartner.html', context)
###############
# Partner views
###############
@permission_required('scipost.can_manage_SPB', return_403=True)
@transaction.atomic
def partner_edit(request, partner_id):
partner = get_object_or_404(Partner, id=partner_id)
# Start/fill forms
form = PartnerForm(request.POST or None, instance=partner)
ContactModelFormset = modelformset_factory(Contact, ContactForm, can_delete=True, extra=0,
formset=ContactFormset)
contact_formset = ContactModelFormset(request.POST or None, partner=partner,
queryset=partner.contact_set.all())
# Validate forms for POST request
if form.is_valid() and contact_formset.is_valid():
form.save()
contact_formset.save()
messages.success(request, 'Partner saved')
return redirect(reverse('partners:partner_edit', args=(partner.id,)))
context = {
'form': form,
'contact_formset': contact_formset
}
return render(request, 'partners/partner_edit.html', context)
@permission_required('scipost.can_manage_SPB', return_403=True)
def partner_add_contact(request, partner_id):
partner = get_object_or_404(Partner, id=partner_id)
form = NewContactForm(request.POST or None, partner=partner)
if form.is_valid():
contact = form.save()
messages.success(request, '<h3>Created contact: %s</h3>Email has been sent.'
% str(contact))
return redirect(reverse('partners:dashboard'))
context = {
'partner': partner,
'form': form
}
return render(request, 'partners/partner_add_contact.html', context)
###################
# Institution Views
###################
@permission_required('scipost.can_manage_SPB', return_403=True)
def institution_edit(request, institution_id):
institution = get_object_or_404(Institution, id=institution_id)
form = InstitutionForm(request.POST or None, instance=institution)
if form.is_valid():
form.save()
return redirect(reverse('partners:dashboard'))
context = {
'form': form
}
return render(request, 'partners/institution_edit.html', context)
###########################
# Prospective Partner Views
###########################
@permission_required('scipost.can_manage_SPB', return_403=True) @permission_required('scipost.can_manage_SPB', return_403=True)
def add_prospective_partner(request): def add_prospective_partner(request):
form = ProspectivePartnerForm(request.POST or None) form = ProspectivePartnerForm(request.POST or None)
...@@ -132,11 +197,11 @@ def email_prospartner_contact(request, contact_id): ...@@ -132,11 +197,11 @@ def email_prospartner_contact(request, contact_id):
if form.is_valid(): if form.is_valid():
comments = 'Email sent to %s.' % str(contact) comments = 'Email sent to %s.' % str(contact)
prospartnerevent = ProspectivePartnerEvent( prospartnerevent = ProspectivePartnerEvent(
prospartner = contact.prospartner, prospartner=contact.prospartner,
event = PROSPECTIVE_PARTNER_EVENT_EMAIL_SENT, event=PROSPECTIVE_PARTNER_EVENT_EMAIL_SENT,
comments = comments, comments=comments,
noted_on = timezone.now(), noted_on=timezone.now(),
noted_by = request.user.contributor) noted_by=request.user.contributor)
prospartnerevent.save() prospartnerevent.save()
if contact.prospartner.status in [PROSPECTIVE_PARTNER_REQUESTED, if contact.prospartner.status in [PROSPECTIVE_PARTNER_REQUESTED,
PROSPECTIVE_PARTNER_ADDED]: PROSPECTIVE_PARTNER_ADDED]:
......
...@@ -47,3 +47,27 @@ input[type="file"] { ...@@ -47,3 +47,27 @@ input[type="file"] {
border: 1px solid rgba(0, 0, 0, 0.15); border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 0.15rem; border-radius: 0.15rem;
} }
// Formset
//
.delete-form-group {
> * {
opacity: 0.5;
color: $brand-danger;
&:last-child {
// The delete button should always be visible
opacity: 1.0;
color: inherit;
}
}
.form-group {
display: none;
&:last-child {
// The delete button should always be visible
display: block;
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment