SciPost Code Repository

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

Work in process, see comments

parent 6629f72d
No related branches found
No related tags found
No related merge requests found
...@@ -26,6 +26,7 @@ class ProspectiveContactInline(admin.TabularInline): ...@@ -26,6 +26,7 @@ class ProspectiveContactInline(admin.TabularInline):
class ProspectivePartnerEventInline(admin.TabularInline): class ProspectivePartnerEventInline(admin.TabularInline):
model = ProspectivePartnerEvent model = ProspectivePartnerEvent
extra = 0
class ProspectivePartnerAdmin(admin.ModelAdmin): class ProspectivePartnerAdmin(admin.ModelAdmin):
......
...@@ -47,8 +47,9 @@ PROSPECTIVE_PARTNER_EVENTS = ( ...@@ -47,8 +47,9 @@ PROSPECTIVE_PARTNER_EVENTS = (
) )
PARTNER_INITIATED = 'Initiated'
PARTNER_STATUS = ( PARTNER_STATUS = (
('Initiated', 'Initiated'), (PARTNER_INITIATED, 'Initiated'),
('Contacted', 'Contacted'), ('Contacted', 'Contacted'),
('Negotiating', 'Negotiating'), ('Negotiating', 'Negotiating'),
('Uninterested', 'Uninterested'), ('Uninterested', 'Uninterested'),
......
...@@ -5,20 +5,109 @@ from django_countries import countries ...@@ -5,20 +5,109 @@ from django_countries import countries
from django_countries.widgets import CountrySelectWidget from django_countries.widgets import CountrySelectWidget
from django_countries.fields import LazyTypedChoiceField from django_countries.fields import LazyTypedChoiceField
from .constants import PARTNER_KINDS from .constants import PARTNER_KINDS, PROSPECTIVE_PARTNER_PROCESSED, CONTACT_TYPES
from .models import Partner, ProspectivePartner, ProspectiveContact, ProspectivePartnerEvent from .models import Partner, ProspectivePartner, ProspectiveContact, ProspectivePartnerEvent,\
Institution, Contact
from scipost.models import TITLE_CHOICES from scipost.models import TITLE_CHOICES
class PartnerForm(forms.ModelForm): class PromoteToPartnerForm(forms.ModelForm):
address = forms.CharField(widget=forms.Textarea(), required=False)
acronym = forms.CharField()
class Meta: class Meta:
model = Partner model = ProspectivePartner
fields = '__all__' fields = (
'kind',
'institution_name',
'country',
)
def promote_to_partner(self):
# Create new instances
institution = Institution(
kind=self.cleaned_data['kind'],
name=self.cleaned_data['institution_name'],
acronym=self.cleaned_data['acronym'],
address=self.cleaned_data['address'],
country=self.cleaned_data['country']
)
institution.save()
partner = Partner(
institution=institution,
main_contact=None
)
partner.save()
# Close Prospect
self.instance.status = PROSPECTIVE_PARTNER_PROCESSED
self.instance.save()
return (partner, institution,)
class PromoteToContactForm(forms.ModelForm):
"""
This form is used to create a new `partners.Contact`
"""
contact_types = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple,
choices=CONTACT_TYPES, required=False)
def __init__(self, *args, **kwargs): class Meta:
super(PartnerForm, self).__init__(*args, **kwargs) model = ProspectiveContact
self.fields['institution_address'].widget = forms.Textarea({'rows': 8, }) fields = (
'title',
'first_name',
'last_name',
'email',
)
def promote_contacts(self, partner):
"""
Promote ProspectiveContact's to Contact's related to a certain Partner.
"""
raise NotImplemented
class PromoteToContactFormset(forms.BaseModelFormSet):
"""
This is a formset to process multiple `PromoteToContactForm`s at the same time
designed for the 'promote prospect to partner' action.
"""
def clean(self):
"""
Check if all CONTACT_TYPES are assigned to at least one contact.
"""
contact_type_keys = list(dict(CONTACT_TYPES).keys())
for form in self.forms:
try:
contact_types = form.cleaned_data['contact_types']
except KeyError:
# Form invalid for `contact_types`
continue
for _type in contact_types:
try:
contact_type_keys.remove(_type)
except ValueError:
# Type-key already removed
continue
if not contact_type_keys:
break
if contact_type_keys:
# Add error to all forms if not all CONTACT_TYPES are assigned
for form in self.forms:
form.add_error('contact_types', "Not all contact types have been selected yet.")
def save(self, *args, **kwargs):
raise DeprecationWarning(("This formset is not meant to used with the default"
" `save` method. User the `promote_contacts` instead."))
def promote_contacts(self, partner):
"""
Promote ProspectiveContact's to Contact's related to a certain Partner.
"""
raise NotImplemented
class ProspectivePartnerForm(forms.ModelForm): class ProspectivePartnerForm(forms.ModelForm):
...@@ -55,17 +144,6 @@ class EmailProspectivePartnerContactForm(forms.Form): ...@@ -55,17 +144,6 @@ class EmailProspectivePartnerContactForm(forms.Form):
{'placeholder': 'Write your message in this box (optional).'}) {'placeholder': 'Write your message in this box (optional).'})
# class ProspectivePartnerContactSelectForm(forms.Form):
# def __init__(self, *args, **kwargs):
# prospartner_id = kwargs.pop('prospartner.id')
# super(ProspectivePartnerContactSelectForm, self).__init(*args, **kwargs)
# self.fields['contact'] = forms.ModelChoiceField(
# queryset=ProspectiveContact.objects.filter(
# prospartner__pk=prospartner_id).order_by('last_name'),
# required=True)
class ProspectivePartnerEventForm(forms.ModelForm): class ProspectivePartnerEventForm(forms.ModelForm):
class Meta: class Meta:
model = ProspectivePartnerEvent model = ProspectivePartnerEvent
......
from django.db import models from django.db import models
from .constants import MEMBERSHIP_SUBMITTED from .constants import MEMBERSHIP_SUBMITTED, PROSPECTIVE_PARTNER_PROCESSED
class ProspectivePartnerManager(models.Manager):
def not_yet_partner(self):
return self.exclude(status=PROSPECTIVE_PARTNER_PROCESSED)
class MembershipAgreementManager(models.Manager): class MembershipAgreementManager(models.Manager):
......
...@@ -14,9 +14,10 @@ from .constants import PROSPECTIVE_PARTNER_EVENT_EMAIL_SENT,\ ...@@ -14,9 +14,10 @@ from .constants import PROSPECTIVE_PARTNER_EVENT_EMAIL_SENT,\
PROSPECTIVE_PARTNER_EVENT_MARKED_AS_UNINTERESTED,\ PROSPECTIVE_PARTNER_EVENT_MARKED_AS_UNINTERESTED,\
PROSPECTIVE_PARTNER_UNINTERESTED,\ PROSPECTIVE_PARTNER_UNINTERESTED,\
PROSPECTIVE_PARTNER_EVENT_PROMOTED,\ PROSPECTIVE_PARTNER_EVENT_PROMOTED,\
PROSPECTIVE_PARTNER_PROCESSED, CONTACT_TYPES PROSPECTIVE_PARTNER_PROCESSED, CONTACT_TYPES,\
PARTNER_INITIATED
from .managers import MembershipAgreementManager from .managers import MembershipAgreementManager, ProspectivePartnerManager
from scipost.constants import TITLE_CHOICES from scipost.constants import TITLE_CHOICES
from scipost.fields import ChoiceArrayField from scipost.fields import ChoiceArrayField
...@@ -39,6 +40,8 @@ class ProspectivePartner(models.Model): ...@@ -39,6 +40,8 @@ class ProspectivePartner(models.Model):
status = models.CharField(max_length=32, choices=PROSPECTIVE_PARTNER_STATUS, status = models.CharField(max_length=32, choices=PROSPECTIVE_PARTNER_STATUS,
default=PROSPECTIVE_PARTNER_ADDED) default=PROSPECTIVE_PARTNER_ADDED)
objects = ProspectivePartnerManager()
def __str__(self): def __str__(self):
return '%s (received %s), %s' % (self.institution_name, return '%s (received %s), %s' % (self.institution_name,
self.date_received.strftime("%Y-%m-%d"), self.date_received.strftime("%Y-%m-%d"),
...@@ -63,7 +66,8 @@ class ProspectiveContact(models.Model): ...@@ -63,7 +66,8 @@ class ProspectiveContact(models.Model):
It does not have a corresponding User object. It does not have a corresponding User object.
It is meant to be used internally at SciPost, during Partner mining. It is meant to be used internally at SciPost, during Partner mining.
""" """
prospartner = models.ForeignKey('partners.ProspectivePartner', on_delete=models.CASCADE) prospartner = models.ForeignKey('partners.ProspectivePartner', on_delete=models.CASCADE,
related_name='prospective_contacts')
title = models.CharField(max_length=4, choices=TITLE_CHOICES) title = models.CharField(max_length=4, choices=TITLE_CHOICES)
first_name = models.CharField(max_length=64) first_name = models.CharField(max_length=64)
last_name = models.CharField(max_length=64) last_name = models.CharField(max_length=64)
...@@ -134,7 +138,7 @@ class Partner(models.Model): ...@@ -134,7 +138,7 @@ class Partner(models.Model):
""" """
institution = models.ForeignKey('partners.Institution', on_delete=models.CASCADE, institution = models.ForeignKey('partners.Institution', on_delete=models.CASCADE,
blank=True, null=True) blank=True, null=True)
status = models.CharField(max_length=16, choices=PARTNER_STATUS) status = models.CharField(max_length=16, choices=PARTNER_STATUS, default=PARTNER_INITIATED)
main_contact = models.ForeignKey('partners.Contact', on_delete=models.CASCADE, main_contact = models.ForeignKey('partners.Contact', on_delete=models.CASCADE,
blank=True, null=True, blank=True, null=True,
related_name='partner_main_contact') related_name='partner_main_contact')
......
{% extends 'scipost/base.html' %}
{% block breadcrumb %}
<nav class="breadcrumb py-md-2 px-0">
<div class="container">
{% block breadcrumb_items %}
<a href="{% url 'partners:dashboard' %}" class="breadcrumb-item">Partner Page</a>
{% endblock %}
</div>
</nav>
{% endblock %}
{% block pagetitle %}: Supporting Partners:{% endblock pagetitle %}
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<th>Actions</th> <th>Actions</th>
</thead> </thead>
<tbody> <tbody>
{% for contact in pp.prospectivecontact_set.all %} {% for contact in pp.prospective_contacts.all %}
<tr> <tr>
<td>{{ contact.role }}</td> <td>{{ contact.role }}</td>
<td>{{ contact.get_title_display }} {{ contact.first_name }} {{ contact.last_name }}</td> <td>{{ contact.get_title_display }} {{ contact.first_name }} {{ contact.last_name }}</td>
...@@ -43,8 +43,7 @@ ...@@ -43,8 +43,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-1"></div> <div class="col-md-6 offset-md-1">
<div class="col-6">
<h3>Events</h3> <h3>Events</h3>
<ul> <ul>
{% for event in pp.prospectivepartnerevent_set.all %} {% for event in pp.prospectivepartnerevent_set.all %}
...@@ -54,13 +53,18 @@ ...@@ -54,13 +53,18 @@
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
<div class="col-5"> <div class="col-md-5">
<h3>Add an event for this Prospective Partner</h3> <h3>Add an event for this Prospective Partner</h3>
<form action="{% url 'partners:add_prospartner_event' prospartner_id=pp.id %}" method="post"> <form class="d-block mt-2 mb-3" action="{% url 'partners:add_prospartner_event' prospartner_id=pp.id %}" method="post">
{% csrf_token %} {% csrf_token %}
{{ ppevent_form|bootstrap }} {{ ppevent_form|bootstrap }}
<input type="submit" name="submit" value="Submit" class="btn btn-secondary"> <input type="submit" name="submit" value="Submit" class="btn btn-secondary">
</form> </form>
<h3>Partner status</h3>
<ul>
<li><a href="{% url 'partners:promote_prospartner' pp.id %}">Upgrade prospect to partner</a></li>
</ul>
</div> </div>
</div> </div>
</div> </div>
{% extends 'partners/_partners_page_base.html' %}
{% block breadcrumb_items %}
{{block.super}}
<span class="breadcrumb-item">Promote Prospect</span>
{% endblock %}
{% block pagetitle %}{{block.super}} Promote Prospect{% endblock pagetitle %}
{% load bootstrap %}
{% block content %}
<div class="row">
<div class="col-12">
<h1 class="highlight">Promote Prospect to Partner</h1>
</div>
</div>
<div class="row">
<div class="col-12">
<form method="post">
{% csrf_token %}
{{ form|bootstrap }}
{{ contact_formset.management_form }}
{% for form in contact_formset %}
<h3>Contact {{forloop.counter}}</h3>
{{ form|bootstrap }}
{% endfor %}
{% for error in contact_formset.non_form_errors %}
<div class="form-group row">
<div class="alert alert-danger show">
<strong>Form error</strong> &middot; {{error}}
</div>
</div>
{% endfor %}
<input class="btn btn-primary" type="submit" value="Submit"/>
</form>
</div>
</div>
{% endblock content %}
...@@ -7,12 +7,14 @@ urlpatterns = [ ...@@ -7,12 +7,14 @@ urlpatterns = [
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'^manage$', views.manage, name='manage'), url(r'^manage$', views.manage, name='manage'),
url(r'^add_prospective_partner$', views.add_prospective_partner, url(r'^prospect_partners/add$', views.add_prospective_partner,
name='add_prospective_partner'), name='add_prospective_partner'),
url(r'^add_prospartner_contact/(?P<prospartner_id>[0-9]+)$', url(r'^prospect_partners/contacts/(?P<contact_id>[0-9]+)/email$',
views.add_prospartner_contact, name='add_prospartner_contact'),
url(r'^email_prospartner_contact/(?P<contact_id>[0-9]+)$',
views.email_prospartner_contact, name='email_prospartner_contact'), views.email_prospartner_contact, name='email_prospartner_contact'),
url(r'^add_prospartner_event/(?P<prospartner_id>[0-9]+)$', url(r'^prospect_partner/(?P<prospartner_id>[0-9]+)/contacts/add$',
views.add_prospartner_contact, name='add_prospartner_contact'),
url(r'^prospect_partner/(?P<prospartner_id>[0-9]+)/promote$',
views.promote_prospartner, name='promote_prospartner'),
url(r'^prospect_partner/(?P<prospartner_id>[0-9]+)/events/add$',
views.add_prospartner_event, name='add_prospartner_event'), views.add_prospartner_event, name='add_prospartner_event'),
] ]
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.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
...@@ -11,8 +12,8 @@ from .constants import PROSPECTIVE_PARTNER_REQUESTED,\ ...@@ -11,8 +12,8 @@ from .constants import PROSPECTIVE_PARTNER_REQUESTED,\
from .models import Partner, ProspectivePartner, ProspectiveContact,\ from .models import Partner, ProspectivePartner, ProspectiveContact,\
ProspectivePartnerEvent, MembershipAgreement ProspectivePartnerEvent, MembershipAgreement
from .forms import ProspectivePartnerForm, ProspectiveContactForm,\ from .forms import ProspectivePartnerForm, ProspectiveContactForm,\
EmailProspectivePartnerContactForm,\ EmailProspectivePartnerContactForm, PromoteToPartnerForm,\
ProspectivePartnerEventForm, MembershipQueryForm ProspectivePartnerEventForm, MembershipQueryForm, PromoteToContactForm, PromoteToContactFormset
from .utils import PartnerUtils from .utils import PartnerUtils
...@@ -57,8 +58,8 @@ def membership_request(request): ...@@ -57,8 +58,8 @@ def membership_request(request):
) )
contact.save() contact.save()
prospartnerevent = ProspectivePartnerEvent( prospartnerevent = ProspectivePartnerEvent(
prospartner = prospartner, prospartner=prospartner,
event = PROSPECTIVE_PARTNER_EVENT_REQUESTED,) event=PROSPECTIVE_PARTNER_EVENT_REQUESTED)
prospartnerevent.save() prospartnerevent.save()
ack_message = ('Thank you for your SPB Membership query. ' ack_message = ('Thank you for your SPB Membership query. '
'We will get back to you in the very near future ' 'We will get back to you in the very near future '
...@@ -85,6 +86,24 @@ def manage(request): ...@@ -85,6 +86,24 @@ def manage(request):
return render(request, 'partners/manage_partners.html', context) return render(request, 'partners/manage_partners.html', context)
@permission_required('scipost.can_manage_SPB', return_403=True)
@transaction.atomic
def promote_prospartner(request, prospartner_id):
prospartner = get_object_or_404(ProspectivePartner.objects.not_yet_partner(),
pk=prospartner_id)
form = PromoteToPartnerForm(request.POST or None, instance=prospartner)
ContactModelFormset = modelformset_factory(ProspectiveContact, PromoteToContactForm,
formset=PromoteToContactFormset)
contact_formset = ContactModelFormset(request.POST or None,
queryset=prospartner.prospective_contacts.all())
if form.is_valid() and contact_formset.is_valid():
partner, institution = form.promote_to_partner()
contact_formset.promote_contacts(partner)
raise NotImplemented
context = {'form': form, 'contact_formset': contact_formset}
return render(request, 'partners/promote_prospartner.html', context)
@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)
......
...@@ -10,6 +10,11 @@ ...@@ -10,6 +10,11 @@
border-color: #d9534f; border-color: #d9534f;
} }
.has-error .multiple-checkbox .help-block {
color: $brand-danger;
font-weight: 600;
}
.form-control + .help-block { .form-control + .help-block {
margin-top: 3px; margin-top: 3px;
display: inline-block; display: inline-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