diff --git a/scipost_django/organizations/managers.py b/scipost_django/organizations/managers.py index e4c5725cd055ac43594eff56e40111cb5a8e679b..a8c96d0f7146e330c69a343ddaf631826cf7fb67 100644 --- a/scipost_django/organizations/managers.py +++ b/scipost_django/organizations/managers.py @@ -5,8 +5,10 @@ __license__ = "AGPL v3" import datetime from django.db import models +from django.db.models import Q from finances.constants import SUBSIDY_PROMISED, SUBSIDY_INVOICED, SUBSIDY_RECEIVED +from finances.models.subsidy import Subsidy class OrganizationQuerySet(models.QuerySet): @@ -62,3 +64,48 @@ class OrganizationQuerySet(models.QuerySet): .annotate(total=models.Sum("subsidy__amount")) .order_by("-total") ) + + def order_by_yearly_coverage(self, year_start=None, year_end=None): + """ + Order by average yearly coverage between year_start and year_end. + If either year_start or year_end is None, assume interminable coverage of that end. + """ + subsidy_filter = Q(organization=models.OuterRef("pk")) + + # If year_start and year_end are both None, no filtering is needed. + # If year_start is None, filter such that date_until <= year_end. + # If year_end is None, filter such that year_start <= date_from. + # If both are specified, filter such that + # date_from <= year_start <= year_end <= date_until. + match (year_start, year_end): + case (None, None): + pass + case (None, _): + subsidy_filter &= Q(date_until__year__lte=year_end) + case (_, None): + subsidy_filter &= Q(date_from__year__gte=year_start) + case (_, _): + subsidy_filter &= Q(date_from__year__lte=year_start) + subsidy_filter &= Q(date_until__year__gte=year_end) + + return ( + self.annotate( + total_yearly_coverage=models.Subquery( + Subsidy.objects.obtained() + .filter(subsidy_filter) + .annotate( + yearly_coverage=models.F("amount") + / ( + 1 + + models.F("date_until__year") + - models.F("date_from__year") + ) + ) + .values("organization") + .annotate(total=models.Sum("yearly_coverage")) + .values("total") + ) + ) + .order_by(models.F("total_yearly_coverage").desc(nulls_last=True)) + .distinct() + ) diff --git a/scipost_django/sponsors/templates/sponsors/_sponsor_card.html b/scipost_django/sponsors/templates/sponsors/_sponsor_card.html index 0104fee7302ecf731801b666aa7ec3d14a534230..cba410c3fde010bdd985ee03cb119ae88cf94b1f 100644 --- a/scipost_django/sponsors/templates/sponsors/_sponsor_card.html +++ b/scipost_django/sponsors/templates/sponsors/_sponsor_card.html @@ -6,9 +6,9 @@ hx-push-url="true" hx-target="body"> - <div class="bg-white m-2 h-100 d-flex" style="min-height: 100px;"> + <div class="bg-white m-2 h-100 d-flex"> - <img class="m-auto p-4 {{ sponsor.css_class }}" style="max-height: 16rem; max-width: 100%; object-fit: contain" + <img class="m-auto p-4 w-100 {{ sponsor.css_class }}" style="max-height: 200px; object-fit: contain" {% if sponsor.logo %} src="{{ sponsor.logo.url }}" {% else %} src="{% static 'organizations/no_logo.jpg' %}" {% endif %} alt="{{ sponsor.name }} logo" /> </div> diff --git a/scipost_django/sponsors/templates/sponsors/sponsors.html b/scipost_django/sponsors/templates/sponsors/sponsors.html index c9ef4fc0ae0167f19b2299030dfa88516fdc8d23..7bbbf7d173bee00d68f6600cb6f62addcd4710d8 100644 --- a/scipost_django/sponsors/templates/sponsors/sponsors.html +++ b/scipost_django/sponsors/templates/sponsors/sponsors.html @@ -2,14 +2,17 @@ {% load render_bundle from webpack_loader %} -{% block meta_description %}{{ block.super }} Sponsors{% endblock meta_description %} -{% block pagetitle %}: Sponsors{% endblock pagetitle %} +{% block meta_description %} + {{ block.super }} Sponsors +{% endblock meta_description %} + +{% block pagetitle %} + : Sponsors +{% endblock pagetitle %} {% load static %} -{% block breadcrumb_items %} - {{ block.super }} -{% endblock %} +{% block breadcrumb_items %}{{ block.super }}{% endblock %} {% block content %} @@ -22,16 +25,18 @@ <div class="row"> <div class="col-12"> <h4> - <strong>We cordially invite organizations worldwide to join our Sponsorship scheme, and make <a href="{% url 'scipost:about' %}#GOA">Genuine Open Access</a> a reality.</strong> + <strong>We cordially invite organizations worldwide to join our Sponsorship scheme, and make <a href="{% url 'scipost:about' %}#GOA">Genuine Open Access</a> a reality.</strong> </h4> - <br/> + <br /> <p> - Is your organization benefitting from SciPost's activities (check our <a href="{% url 'organizations:organizations' %}">organizations page</a>), and does it not appear in our list of Sponsors below? Then consider helping SciPost: - <br/> - <strong>Are you a scientist?</strong><br/> - Please petition your local librarian/director/... to consider sponsoring us. You can use this email <a href="mailto:?subject=Petition to support SciPost&body={% autoescape on %}{% include 'sponsors/sponsor_petition_email.html' %}{% endautoescape %}&cc=sponsors@{{ request.get_host }}">template</a>. - <strong>Are you a librarian, funding agency representative or other potential supporter?</strong><br/> - Take a look at our <a href="{% static 'sponsors/SciPost_Sponsorship_Agreement.pdf' %}">Sponsorship Agreement</a> template, and contact us at <a href="mailto:sponsors@{{ request.get_host }}?subject=Sponsors enquiry">sponsors@{{ request.get_host }}</a> to enquire about further details or initiate your sponsorship. + Is your organization benefitting from SciPost's activities (check our <a href="{% url 'organizations:organizations' %}">organizations page</a>), and does it not appear in our list of Sponsors below? Then consider helping SciPost: + <br /> + <strong>Are you a scientist?</strong> + <br /> + Please petition your local librarian/director/... to consider sponsoring us. You can use this email <a href="mailto:?subject=Petition to support SciPost&body={% autoescape on %}{% include 'sponsors/sponsor_petition_email.html' %}{% endautoescape %}&cc=sponsors@{{ request.get_host }}">template</a>. + <strong>Are you a librarian, funding agency representative or other potential supporter?</strong> + <br /> + Take a look at our <a href="{% static 'sponsors/SciPost_Sponsorship_Agreement.pdf' %}">Sponsorship Agreement</a> template, and contact us at <a href="mailto:sponsors@{{ request.get_host }}?subject=Sponsors enquiry">sponsors@{{ request.get_host }}</a> to enquire about further details or initiate your sponsorship. </p> </div> </div> @@ -39,99 +44,106 @@ <div class="row"> <div class="col-md-4"> <div class="card"> - <div class="card-header"> - <h3>Community service; no user fees</h3> - </div> - <div class="card-body"> - <p>SciPost does not charge any subscription fees or article processing charges (APCs): all our operations are performed as a community service, with no user-facing charges;</p> - <p>Our initiative's scope is resolutely international: we do not impose any geographical or institutional restrictions on the delivery of our services;</p> - <p>SciPost is dedicated to serving the academic community, with no further competing interests.</p> - </div> + <div class="card-header"> + <h3>Community service; no user fees</h3> + </div> + <div class="card-body"> + <p> + SciPost does not charge any subscription fees or article processing charges (APCs): all our operations are performed as a community service, with no user-facing charges; + </p> + <p> + Our initiative's scope is resolutely international: we do not impose any geographical or institutional restrictions on the delivery of our services; + </p> + <p>SciPost is dedicated to serving the academic community, with no further competing interests.</p> + </div> </div> </div> <div class="col-md-4"> <div class="card"> - <div class="card-header"> - <h3>Quality through Openness</h3> - </div> - <div class="card-body"> - <p>Our sharp focus on openness through our <a href="/FAQ#pwr">peer-witnessed refereeing</a> procedure equips us with arguably the most stringent editorial quality control system available;</p> - <p>Our <a href="{% url 'scipost:about' %}#editorial_college_physics">Editorial College</a> is composed of a broad selection of top academics;</p> - <p>Our fully professional publishing services meet or surpass best practices in all respects. Our flagship journal SciPost Physics has been awarded the <a href="https://doaj.org">DOAJ</a> Seal.</p> - </div> + <div class="card-header"> + <h3>Quality through Openness</h3> + </div> + <div class="card-body"> + <p> + Our sharp focus on openness through our <a href="/FAQ#pwr">peer-witnessed refereeing</a> procedure equips us with arguably the most stringent editorial quality control system available; + </p> + <p> + Our <a href="{% url 'scipost:about' %}#editorial_college_physics">Editorial College</a> is composed of a broad selection of top academics; + </p> + <p> + Our fully professional publishing services meet or surpass best practices in all respects. Our flagship journal SciPost Physics has been awarded the <a href="https://doaj.org">DOAJ</a> Seal. + </p> + </div> </div> </div> <div class="col-md-4"> <div class="card"> - <div class="card-header"> - <h3>Community funded</h3> - </div> - <div class="card-body"> - <p>Our operations are obviously not without cost, but our strictly not-for-profit setup, community-led workflow, streamlined infrastructure and no-frills administration mean that the average per-publication costs are much lower than those of competing services;</p> - <p>Our financing model relies on sponsorship from the organizations which benefit from our activities (see our <a href="{% url 'organizations:organizations' %}">organizations page</a>);</p> - <p>All sponsorship funds are pooled and exclusively used to run our infrastructure and services.</p> - </div> + <div class="card-header"> + <h3>Community funded</h3> + </div> + <div class="card-body"> + <p> + Our operations are obviously not without cost, but our strictly not-for-profit setup, community-led workflow, streamlined infrastructure and no-frills administration mean that the average per-publication costs are much lower than those of competing services; + </p> + <p> + Our financing model relies on sponsorship from the organizations which benefit from our activities (see our <a href="{% url 'organizations:organizations' %}">organizations page</a>); + </p> + <p>All sponsorship funds are pooled and exclusively used to run our infrastructure and services.</p> + </div> </div> </div> </div> <div class="row"> <div class="col-12"> - <h3>We aim to establish a healthier <a href="{% url 'finances:business_model' %}" target="_blank">business model</a> for scientific publishing</h3> - <p>We are able to run a fully sustainable infrastructure at an estimated cost of under €400 per publication, much below the current norm for APCs. The more scientists shift their publishing to SciPost, the fewer subscription/article processing charges you will have to pay as an organization. Your sponsorship will help us scale up and make our initiative sustainable.</p> + <h3> + We aim to establish a healthier <a href="{% url 'finances:business_model' %}" target="_blank">business model</a> for scientific publishing + </h3> + <p> + We are able to run a fully sustainable infrastructure at an estimated cost of under €400 per publication, much below the current norm for APCs. The more scientists shift their publishing to SciPost, the fewer subscription/article processing charges you will have to pay as an organization. Your sponsorship will help us scale up and make our initiative sustainable. + </p> </div> </div> <div class="row" hx-boost="true"> <div class="col-12"> - <h1 class="highlight">Our current Sponsors</h1> - - <h3 class="highlight">€20k and above:</h3> - <div class="d-grid gap-3" style="grid-template-columns: repeat(3, minmax(0, 1fr));"> - {% for sponsor in sponsors_20kplus %} - {% include 'sponsors/_sponsor_card.html' with sponsor=sponsor %} - {% endfor %} - </div> - - <h3 class="highlight mt-4">€10k and above:</h3> - <div class="d-grid gap-3" style="grid-template-columns: repeat(3, minmax(0, 1fr));"> - {% for sponsor in sponsors_10kplus %} - {% include 'sponsors/_sponsor_card.html' with sponsor=sponsor %} - {% endfor %} - </div> + <h1 class="highlight">Our recent Sponsors</h1> - <h3 class="highlight mt-4">€5k and above:</h3> - <div class="d-grid gap-3" style="grid-template-columns: repeat(3, minmax(0, 1fr));"> - {% for sponsor in sponsors_5kplus %} - {% include 'sponsors/_sponsor_card.html' with sponsor=sponsor %} - {% endfor %} - </div> - - <h3 class="highlight mt-4">Our other current Sponsors:</h3> - <div class="d-grid gap-3" style="grid-template-columns: repeat(3, minmax(0, 1fr));"> - {% for sponsor in current_sponsors %} - {% include 'sponsors/_sponsor_card.html' with sponsor=sponsor %} - {% endfor %} + <hgroup class="p-2 highlight d-flex align-items-center justify-content-between"> + <h3 class="m-0">Current Sponsors</h3> + </hgroup> + <div class="d-grid gap-3" + style="grid-template-columns: repeat(3, minmax(0, 1fr))"> + {% for sponsor in current_sponsors %} + {% include 'sponsors/_sponsor_card.html' with sponsor=sponsor %} + {% endfor %} </div> - <h1 class="highlight">Our recent-past Sponsors</h1> - - <hgroup class="p-2 highlight d-flex align-items-center justify-content-between"> - <h3 class="m-0"> - Last year's Sponsors: - </h3> - <span class="text-muted"> - (excludes current sponsors) - </span> + <hgroup class="p-3 mt-4 highlight d-flex align-items-center justify-content-between"> + <h3 class="m-0">Last year's Sponsors:</h3> + <span class="text-muted">(excludes current sponsors)</span> </hgroup> - <div class="d-grid gap-3" style="grid-template-columns: repeat(3, minmax(0, 1fr));"> - {% for sponsor in last_year_sponsors %} - {% include 'sponsors/_sponsor_card.html' with sponsor=sponsor %} - {% endfor %} + <div class="d-grid gap-3" + style="grid-template-columns: repeat(3, minmax(0, 1fr))"> + {% for sponsor in last_year_sponsors %} + {% include 'sponsors/_sponsor_card.html' with sponsor=sponsor %} + {% endfor %} </div> + <details class="mt-4"> + <summary class="highlight list-triangle p-3"> + <h1 class="m-0">Our past Sponsors</h1> + </summary> + + <div class="d-grid gap-3" + style="grid-template-columns: repeat(3, minmax(0, 1fr))"> + {% for sponsor in past_sponsors %} + {% include 'sponsors/_sponsor_card.html' with sponsor=sponsor %} + {% endfor %} + </div> + </details> + </div> </div> - {% endblock content %} diff --git a/scipost_django/sponsors/views.py b/scipost_django/sponsors/views.py index b4684c6e6454adbb3c2f097e86e90e12166083b6..3dde57110701bcd2a2ad4bdf6ce2821214fd8521 100644 --- a/scipost_django/sponsors/views.py +++ b/scipost_django/sponsors/views.py @@ -9,26 +9,28 @@ from organizations.models import Organization def sponsors(request): - sponsors_20kplus = Organization.objects.with_subsidy_above_and_up_to(20000) - sponsors_10kplus = Organization.objects.with_subsidy_above_and_up_to(10000, 20000) - sponsors_5kplus = Organization.objects.with_subsidy_above_and_up_to(5000, 10000) - current_sponsors = ( - Organization.objects.current_sponsors().with_subsidy_above_and_up_to(0, 5000) - ) + year = datetime.date.today().year + + current_sponsors = Organization.objects.current_sponsors() last_year_sponsors = ( Organization.objects.all_sponsors() .filter( - subsidy__date_until__year__lte=datetime.date.today().year - 1, - subsidy__date_until__gt=datetime.date.today() - - datetime.timedelta(days=365), + subsidy__date_from__year__lte=year - 1, + subsidy__date_until__year__gte=year - 1, ) - .exclude(pk__in=current_sponsors.values_list("pk", flat=True)) + .exclude(id__in=current_sponsors.values_list("id", flat=True)) ) + past_sponsors = ( + Organization.objects.all_sponsors() + .exclude(id__in=current_sponsors.values_list("id", flat=True)) + .exclude(id__in=last_year_sponsors.values_list("id", flat=True)) + ) + context = { - "sponsors_20kplus": sponsors_20kplus, - "sponsors_10kplus": sponsors_10kplus, - "sponsors_5kplus": sponsors_5kplus, - "current_sponsors": current_sponsors.order_by_total_amount_received(), - "last_year_sponsors": last_year_sponsors.order_by_total_amount_received(), + "current_sponsors": current_sponsors.order_by_yearly_coverage(year, year), + "last_year_sponsors": last_year_sponsors.order_by_yearly_coverage( + year - 1, year - 1 + ), + "past_sponsors": past_sponsors.order_by_yearly_coverage(None, year - 2), } return render(request, "sponsors/sponsors.html", context)