From be1a8beb00966b307deefad13955a0e4640159eb Mon Sep 17 00:00:00 2001 From: pdelboca Date: Tue, 11 Jun 2024 14:22:41 +0200 Subject: [PATCH 1/7] Remove no longer needed Person model. --- foundation/organisation/admin.py | 15 +-- foundation/organisation/forms.py | 6 - foundation/organisation/models.py | 103 ------------------ foundation/organisation/search_indexes.py | 14 --- .../organisation/networkgroup_text.txt | 13 --- .../indexes/organisation/person_text.txt | 12 -- .../indexes/organisation/project_text.txt | 9 -- .../templates/search/organisation/person.html | 28 ----- foundation/organisation/tests/__init__.py | 0 foundation/organisation/tests/test_admin.py | 1 - foundation/organisation/tests/test_api.py | 22 ---- .../organisation/tests/test_search_indexes.py | 17 --- foundation/organisation/utils.py | 21 ---- foundation/organisation/views.py | 13 --- 14 files changed, 1 insertion(+), 273 deletions(-) delete mode 100644 foundation/organisation/forms.py delete mode 100644 foundation/organisation/search_indexes.py delete mode 100644 foundation/organisation/templates/search/indexes/organisation/networkgroup_text.txt delete mode 100644 foundation/organisation/templates/search/indexes/organisation/person_text.txt delete mode 100644 foundation/organisation/templates/search/indexes/organisation/project_text.txt delete mode 100644 foundation/organisation/templates/search/organisation/person.html delete mode 100644 foundation/organisation/tests/__init__.py delete mode 100644 foundation/organisation/tests/test_admin.py delete mode 100644 foundation/organisation/tests/test_api.py delete mode 100644 foundation/organisation/tests/test_search_indexes.py delete mode 100644 foundation/organisation/utils.py delete mode 100644 foundation/organisation/views.py diff --git a/foundation/organisation/admin.py b/foundation/organisation/admin.py index 96c53725..f49ab6d4 100644 --- a/foundation/organisation/admin.py +++ b/foundation/organisation/admin.py @@ -1,22 +1,9 @@ -import reversion - from django.contrib import admin -from .forms import PersonForm -from .models import Person, SideBarExtension +from .models import SideBarExtension from cms.extensions import PageExtensionAdmin -class PersonAdmin(reversion.admin.VersionAdmin): - list_display = ('name', 'email', 'twitter', 'username_on_slack') - ordering = ('name',) - search_fields = ('name', 'email') - form = PersonForm - - -admin.site.register(Person, PersonAdmin) - - class SideBarExtensionAdmin(PageExtensionAdmin): pass diff --git a/foundation/organisation/forms.py b/foundation/organisation/forms.py deleted file mode 100644 index 9f67f394..00000000 --- a/foundation/organisation/forms.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.forms import ModelForm -from foundation.core.svg import SvgAndImageFormField - - -class PersonForm(ModelForm): - photo = SvgAndImageFormField() diff --git a/foundation/organisation/models.py b/foundation/organisation/models.py index eaaefe6b..1e618291 100644 --- a/foundation/organisation/models.py +++ b/foundation/organisation/models.py @@ -1,115 +1,12 @@ import logging -from hashlib import md5 from cms.models.pluginmodel import CMSPlugin from cms.extensions import PageExtension from django.db import models - logger = logging.getLogger(__name__) -class Person(models.Model): - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) - - name = models.CharField(max_length=100) - username_on_slack = models.CharField(max_length=100, blank=True, null=True) - description = models.TextField(blank=True, null=True) - email = models.EmailField(blank=True) - photo = models.ImageField(upload_to="organisation/people/photos", blank=True) - twitter = models.CharField(max_length=18, blank=True) - url = models.URLField(blank=True) - - NOWDOING_DEFAULT_ORDER = ( - "working", - "location", - "reading", - "listening", - "watching", - "eating", - ) - - def __str__(self): - return self.name - - @property - def gravatar_url(self): - """Returns the gravatar url for this user (constructed from email)""" - base = "https://gravatar.com/avatar/{hash}?s=132" - md5_hash = md5(self.email.strip().lower().encode("utf-8")).hexdigest() - return base.format(hash=md5_hash) - - @property - def nowdoing_with_latest(self): - """All NowDoing attributes of the user with the most recently - updated one marked with `is_newest_update`""" - nowdoings = self.nowdoing_set.all().extra(order_by=["-updated_at"]) - if nowdoings: - nowdoings[0].is_newest_update = True - return nowdoings - - @property - def nowdoing_by_custom_order(self, custom_order=None): - custom_order = custom_order or self.NOWDOING_DEFAULT_ORDER - nowdoings = self.nowdoing_with_latest - ordered_nowdoings = list() - for doing_type in custom_order: - if nowdoings.filter(doing_type=doing_type): - ordered_nowdoings.append( - nowdoings.filter(doing_type=doing_type).first() - ) - return ordered_nowdoings - - @property - def has_anything_to_show(self): - """Is there anything that we can show for this person in the - template (other then email which is checked separately)""" - return self.url or self.twitter or self.nowdoing_set.count() - - class Meta: - ordering = ["name"] - verbose_name_plural = "people" - - -class NowDoing(models.Model): - ACTIVITIES = ( - ("reading", "reading"), - ("listening", "listening"), - ("working", "working"), - ("location", "location"), - ("watching", "watching"), - ("eating", "eating"), - ) - person = models.ForeignKey(Person, on_delete=models.CASCADE) - doing_type = models.CharField(max_length=10, choices=ACTIVITIES) - link = models.URLField(blank=True, null=True) - text = models.TextField(blank=True, null=True) - updated_at = models.DateTimeField(auto_now=True) - - @property - def icon_name(self): - """The name of the corresponding css icon class""" - matching = {"watching": "playing"} - return matching.get(self.doing_type, self.doing_type) - - @property - def display_name(self): - """The human readable string to be displayed in templates""" - matching = { - "reading": "Reading", - "listening": "Listening to", - "working": "Working on", - "location": "Location", - "watching": "Watching", - "eating": "Eating", - } - return matching.get(self.doing_type, self.doing_type) - - def __repr__(self): - return "".format(self.person.name, self.doing_type) - - class SignupForm(CMSPlugin): title = models.CharField(max_length=50, default="Get Connected to Open Knowledge") description = models.TextField(blank=True) diff --git a/foundation/organisation/search_indexes.py b/foundation/organisation/search_indexes.py deleted file mode 100644 index 333011c3..00000000 --- a/foundation/organisation/search_indexes.py +++ /dev/null @@ -1,14 +0,0 @@ -from haystack import indexes -from .models import Person - - -class PersonIndex(indexes.SearchIndex, indexes.Indexable): - text = indexes.CharField(document=True, use_template=True) - twitter = indexes.CharField(model_attr='twitter') - url = indexes.CharField(model_attr='url') - - def get_model(self): - return Person - - def get_updated_field(self): - return 'updated_at' diff --git a/foundation/organisation/templates/search/indexes/organisation/networkgroup_text.txt b/foundation/organisation/templates/search/indexes/organisation/networkgroup_text.txt deleted file mode 100644 index c1ef14c9..00000000 --- a/foundation/organisation/templates/search/indexes/organisation/networkgroup_text.txt +++ /dev/null @@ -1,13 +0,0 @@ -{{ object.name }} -{{ object.description }} -{{ object.get_group_type_display }} -{{ object.get_country_display }} -{% if object.region %} -{{ object.region }} -{% endif %} -{{ object.extra_information }} -{% for group in object.working_group.all %} -{{ group.name }} -{{ group.description }} -{% endfor %} - diff --git a/foundation/organisation/templates/search/indexes/organisation/person_text.txt b/foundation/organisation/templates/search/indexes/organisation/person_text.txt deleted file mode 100644 index 833aebfd..00000000 --- a/foundation/organisation/templates/search/indexes/organisation/person_text.txt +++ /dev/null @@ -1,12 +0,0 @@ -{{ object.name }} -{% for member in object.unitmembership_set.all %} -{{ member.title }} -{{ member.unit.name }} -{% endfor %} -{% for member in object.boardmembership_set.all %} -{{ member.title }} -{{ member.board.name }} -{% endfor %} -{{ object.description }} -{{ object.twitter }} -{{ object.url }} diff --git a/foundation/organisation/templates/search/indexes/organisation/project_text.txt b/foundation/organisation/templates/search/indexes/organisation/project_text.txt deleted file mode 100644 index eba4d80f..00000000 --- a/foundation/organisation/templates/search/indexes/organisation/project_text.txt +++ /dev/null @@ -1,9 +0,0 @@ -{{ object.name }} -{{ object.description }} -{{ object.twitter }} -{{ object.homepage_url }} -{{ object.forum_url }} -{{ object.sourcecode_url }} -{% for type in object.types.all %} -{{ type.name }} -{% endfor %} diff --git a/foundation/organisation/templates/search/organisation/person.html b/foundation/organisation/templates/search/organisation/person.html deleted file mode 100644 index 1d6dc981..00000000 --- a/foundation/organisation/templates/search/organisation/person.html +++ /dev/null @@ -1,28 +0,0 @@ -{% load markdown_deux_tags %} -{% load thumbnail %} -{% load static %} - - -

{{ person.name }}

-
- -{% for membership in person.unitmembership_set.all %} - - {{ membership.unit.name }} {% if membership.title %} ({{ membership.title }}) {% endif %} - -{% endfor %} - -{% for membership in person.boardmembership_set.all %} - - {{ membership.board.name }} {% if membership.title %} ({{ membership.title }}) {% endif %} - -{% endfor %} - -{% for membership in person.networkgroupmembership_set.all %} - - {{ membership.networkgroup.name }} {% if membership.title %} ({{ membership.title }}) {% endif %} - -{% endfor %} - -

{{ person.description|markdown|truncatewords_html:50 }}

- \ No newline at end of file diff --git a/foundation/organisation/tests/__init__.py b/foundation/organisation/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/foundation/organisation/tests/test_admin.py b/foundation/organisation/tests/test_admin.py deleted file mode 100644 index fdddd233..00000000 --- a/foundation/organisation/tests/test_admin.py +++ /dev/null @@ -1 +0,0 @@ -from .. import admin # noqa diff --git a/foundation/organisation/tests/test_api.py b/foundation/organisation/tests/test_api.py deleted file mode 100644 index 79852a13..00000000 --- a/foundation/organisation/tests/test_api.py +++ /dev/null @@ -1,22 +0,0 @@ -from unittest import TestCase - -from ..utils import get_activity - - -class HelpersTest(TestCase): - def test_get_activity_extracts_type(self): - text = '#reading http://goodreads.com/123' - self.assertEqual(get_activity(text), 'reading') - - text2 = '#watching http://goodreads.com/123' - self.assertEqual(get_activity(text2), 'watching') - - text3 = '#eating http://goodreads.com/123' - self.assertEqual(get_activity(text3), 'eating') - - text4 = '#working http://goodreads.com/123' - self.assertEqual(get_activity(text4), 'working') - - def test_get_activity_returns_none_when_no_match(self): - text = '#foobaring http://goodreads.com/123' - self.assertEqual(get_activity(text), None) diff --git a/foundation/organisation/tests/test_search_indexes.py b/foundation/organisation/tests/test_search_indexes.py deleted file mode 100644 index bd6523df..00000000 --- a/foundation/organisation/tests/test_search_indexes.py +++ /dev/null @@ -1,17 +0,0 @@ -from django.test import TestCase - -from ..models import Person -from ..search_indexes import PersonIndex - - -class PersonIndexTest(TestCase): - def setUp(self): - self.leonardo = Person.objects.create( - name="Leonardo (Leo)", - description='Turtle with a blue mask', - email='leonardo@tmnt.org') - - def test_queryset_includes_person(self): - index = PersonIndex() - - self.assertIn(self.leonardo, index.index_queryset()) diff --git a/foundation/organisation/utils.py b/foundation/organisation/utils.py deleted file mode 100644 index 41da0cb8..00000000 --- a/foundation/organisation/utils.py +++ /dev/null @@ -1,21 +0,0 @@ -import re - -from django.http import JsonResponse - -from foundation.organisation.models import NowDoing - - -def get_activity(text): - options = '|'.join([a[0] for a in NowDoing.ACTIVITIES]) - pattern = '^#({}).*'.format(options) - matches = re.findall(pattern, text) - if matches: - return matches[0] - return None - - -def fail_json(message, status_code=400): - response = JsonResponse({'success': False, - 'message': message}) - response.status_code = status_code - return response diff --git a/foundation/organisation/views.py b/foundation/organisation/views.py deleted file mode 100644 index b68ab88e..00000000 --- a/foundation/organisation/views.py +++ /dev/null @@ -1,13 +0,0 @@ -from django.views.generic.detail import DetailView -from django.shortcuts import get_object_or_404 - -from .models import Person - - -class PersonView(DetailView): - model = Person - template_name = 'organisation/member_detail.html' - - def get_object(self, *args, **kwargs): - person_id = self.kwargs.get('person_id', '') - return get_object_or_404(Person, id=person_id) From 0455ae5187ba2f79ec7d9892ac5763fa92562230 Mon Sep 17 00:00:00 2001 From: pdelboca Date: Tue, 11 Jun 2024 14:24:46 +0200 Subject: [PATCH 2/7] Add migration to remove person/nowdoing model. --- .../0027_delete_nowdoing_delete_person.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 foundation/organisation/migrations/0027_delete_nowdoing_delete_person.py diff --git a/foundation/organisation/migrations/0027_delete_nowdoing_delete_person.py b/foundation/organisation/migrations/0027_delete_nowdoing_delete_person.py new file mode 100644 index 00000000..21f5bc0e --- /dev/null +++ b/foundation/organisation/migrations/0027_delete_nowdoing_delete_person.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.3 on 2024-06-11 12:24 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('organisation', '0026_remove_networkgrouplist_cmsplugin_ptr_and_more'), + ] + + operations = [ + migrations.DeleteModel( + name='NowDoing', + ), + migrations.DeleteModel( + name='Person', + ), + ] From b35b8a2c96ce1fb16638e8075d3880d4fdbb7a4b Mon Sep 17 00:00:00 2001 From: avdata99 Date: Tue, 2 Jul 2024 09:11:39 -0300 Subject: [PATCH 3/7] Ensure base image is upgraded --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index cb13a4be..7e3adcb0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM python:3.10-buster MAINTAINER Open Knowledge Foundation WORKDIR /app -RUN apt-get update +RUN apt-get update -y && apt-get upgrade -y RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash RUN apt-get install -y nginx RUN apt-get install -y supervisor From 411df513fd69e1187496a2d71bb81b282f5702b3 Mon Sep 17 00:00:00 2001 From: pdelboca Date: Thu, 7 Nov 2024 15:28:58 +0100 Subject: [PATCH 4/7] Implement footer with inline css --- static/images/footer/bluesky.svg | 3 + static/images/footer/flickr.svg | 3 + static/images/footer/github.svg | 3 + static/images/footer/linkedin.svg | 3 + static/images/footer/mailinglist.svg | 5 + static/images/footer/mastodon.svg | 3 + static/images/footer/subfooter/ckan-dpg.svg | 74 +++++++++++++ .../footer/subfooter/creative-commons.png | Bin 0 -> 2183 bytes static/images/footer/subfooter/dpga.png | Bin 0 -> 3801 bytes static/images/footer/subfooter/license.png | Bin 0 -> 1830 bytes static/images/footer/subfooter/ngo-source.png | Bin 0 -> 2634 bytes static/images/footer/x.svg | 3 + static/images/footer/youtube.svg | 3 + templates/includes/footer.html | 101 ++++++++++-------- 14 files changed, 159 insertions(+), 42 deletions(-) create mode 100644 static/images/footer/bluesky.svg create mode 100644 static/images/footer/flickr.svg create mode 100644 static/images/footer/github.svg create mode 100644 static/images/footer/linkedin.svg create mode 100644 static/images/footer/mailinglist.svg create mode 100644 static/images/footer/mastodon.svg create mode 100644 static/images/footer/subfooter/ckan-dpg.svg create mode 100644 static/images/footer/subfooter/creative-commons.png create mode 100644 static/images/footer/subfooter/dpga.png create mode 100644 static/images/footer/subfooter/license.png create mode 100644 static/images/footer/subfooter/ngo-source.png create mode 100644 static/images/footer/x.svg create mode 100644 static/images/footer/youtube.svg diff --git a/static/images/footer/bluesky.svg b/static/images/footer/bluesky.svg new file mode 100644 index 00000000..7db4dc8c --- /dev/null +++ b/static/images/footer/bluesky.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/images/footer/flickr.svg b/static/images/footer/flickr.svg new file mode 100644 index 00000000..cc841274 --- /dev/null +++ b/static/images/footer/flickr.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/images/footer/github.svg b/static/images/footer/github.svg new file mode 100644 index 00000000..160cae4a --- /dev/null +++ b/static/images/footer/github.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/images/footer/linkedin.svg b/static/images/footer/linkedin.svg new file mode 100644 index 00000000..a2c10ed1 --- /dev/null +++ b/static/images/footer/linkedin.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/images/footer/mailinglist.svg b/static/images/footer/mailinglist.svg new file mode 100644 index 00000000..dd34ac67 --- /dev/null +++ b/static/images/footer/mailinglist.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/images/footer/mastodon.svg b/static/images/footer/mastodon.svg new file mode 100644 index 00000000..dea389ef --- /dev/null +++ b/static/images/footer/mastodon.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/images/footer/subfooter/ckan-dpg.svg b/static/images/footer/subfooter/ckan-dpg.svg new file mode 100644 index 00000000..9b690d15 --- /dev/null +++ b/static/images/footer/subfooter/ckan-dpg.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/images/footer/subfooter/creative-commons.png b/static/images/footer/subfooter/creative-commons.png new file mode 100644 index 0000000000000000000000000000000000000000..483e5a952be2b27603581098822949c37424ca21 GIT binary patch literal 2183 zcmV;22zd92P)W~)+aD9G{8_%aqR{Y;uG+w z=@G1~tYBwnhv(0qqp`6O@p}$HUA#TtFMZ+l8&9aI4TF@tR>%j94{%`5epFXg^YN%r zqu}J^gb5QSK#ZxVt*u3Qc{v*HH{i%$k09rC4nn^ABdl$#AvI^e%|mihG8`Qoaq`4T z%$hYz>GSI9YD7dtAXU7s`}gi+^&eJ4N`6+z>Z<=@Ps|>)G`GOQ!UECJ(O9@}Ax4Z) z&PlYDN=r+ze*JnB7Z>B}uQp@JvM=HNg*T+4>OWOTNlL-8rOR;W&>;*TKAguFFJ6pm z*RJ96<;$?Pw#Knz$1rW$Gz13+LtkGHi~M{b6|Y?H55<)jyEB$WB=Kr$YT)PR*H1)3 zU0t0owy>}ed-v`Yw~!jrl2cKXUj!+5_UsuB#2tW*jSUVSJg5+{tE&rTWo4+ZuSaup zvts_bb?aEj%ztE}uC@+R(NDQ6$N8sV~+Du(Qj*?`DZR} ze}5acwzk;6e?JBbiBH=rK0Y2NGfp5lG#IL+^52!5RA$bcsTgZ-Z-URlvb`8T zemrK+o{gI~Z}Mk}iHUquG(L6eR9w1riN{S%P2u6;!8cf0Q2|xb)zyuWqJJGM^fweo zr9w$aGTX%T>C+Wsv@&C2V&LH5z(KpNu8uQ!T3Q;`tXab%hJ}TpwY3$iSN~)&yuEv~ z_V@q04OKE)G~4&?-BTK)gtKMK7EW{o#Y)QBeW|Ldg0{AF1)Nfh>=0uOL0*xdCn!%I zsi~+AD71@X4FHjYOI2E7%_xN+kk zmX&B~YT{!Om!Qx4W>GyoJ;gaxiJQ!g9~dRGH|5l+Q@C;C24o&GNQIJ+x;i@ie)n## zZ6QG|EiE~aQzj-3A3lt8=g#peB*;G!7cX9911z^4j~_qoJ%@@*M%_`mkj;}}_wL;f#Is=usXc0m&=UMbRovNlD=Z zv}n;H1O){tY)N{0dhaiM z-y8-VU0q0tnxnI$@TbVlih<~e9k_n=I`-|`$E)p^KmxnCxM0k$#vtxsoEST`AQg3Y z>hNuL4$4Z(@Z`x82;z^9whnAQw1u088(dsnAT>L-Ztq{&%$q+C71t~HNza`-7xwn{ z7z|YB(uyVAJ!O)LNLYMefq=EY2iEpBPzb93hnjwsjf1lzJiR^H7m_KbhQ*uG($az@ zOO|kkG&3`U$2<=VB_!b65Rp*R($W&-%~mS?AWo0jg5t`ukUM5 zMBABye^F5pZ|Q9@Q5?h4)4oA*Q8C_Ss7po&eP8~8{zAs-xc_I@{4|@$bWo2D415!0XW?f#t>WCh6&ada-^Mzlq*CWHe zCG2a(-$@0Pr}A=;T4~fop(25-SZHV{H=F4tHT{Gns6q+e5CYdJu6#^gpMnboC@(EX ztyqs!7llkZ&9nZ{hUd9Vc7};ruV<_m8>l-YcPgqMDap>x=AJ3}Rwo}RdR^(w-{ z!?A7KHujV+cc-M_l|ss)WbyfL7l~=@X9&VBc#rCUhY}$UJoTC zBpGt5r>V4cb#>*;O>RSD;4ev4#(+ZhMNB9#w=jo+_+7EO7%R!{G&D4zt)&fb3o0BY zPoB&*eojsf;^N|Xg^Gz$sU0XagA(#RKMx5>_2JO%fp?Sk9|5|~lR|Bw6AJ(U002ov JPDHLkV1i1F1K0on literal 0 HcmV?d00001 diff --git a/static/images/footer/subfooter/dpga.png b/static/images/footer/subfooter/dpga.png new file mode 100644 index 0000000000000000000000000000000000000000..4230e6efbb04090317d5ef2b2f9e8b1e397be592 GIT binary patch literal 3801 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 P00000NkvXXu0mjf0c0wz literal 0 HcmV?d00001 diff --git a/static/images/footer/subfooter/license.png b/static/images/footer/subfooter/license.png new file mode 100644 index 0000000000000000000000000000000000000000..8639142e5de1ab32c8ae1969d1f122fda8ea1dcc GIT binary patch literal 1830 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 + + diff --git a/static/images/footer/youtube.svg b/static/images/footer/youtube.svg new file mode 100644 index 00000000..3f483655 --- /dev/null +++ b/static/images/footer/youtube.svg @@ -0,0 +1,3 @@ + + + diff --git a/templates/includes/footer.html b/templates/includes/footer.html index f34d3119..2489d773 100644 --- a/templates/includes/footer.html +++ b/templates/includes/footer.html @@ -1,47 +1,64 @@ {% load static %} -