Skip to content

Commit 3e77a70

Browse files
committed
Handle uploading organizer photos
1 parent 128f046 commit 3e77a70

File tree

9 files changed

+97
-13
lines changed

9 files changed

+97
-13
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ dmypy.json
131131
############################################
132132
/node_modules/
133133
/staticfiles/
134-
/pythonsd/media/
134+
/media/
135135
/pythonsd/static/css/
136136
/GIT_COMMIT
137137
/BUILD_DATE

assets/src/sass/_theme.scss

+6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616
vertical-align: -.125rem;
1717
}
1818

19+
.icon-1-5x {
20+
width: 1.5rem;
21+
height: 1.5rem;
22+
vertical-align: -.125rem;
23+
}
24+
1925
.icon-2x {
2026
width: 2rem;
2127
height: 2rem;

config/urls.py

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from django.contrib import admin
33
from django.urls import include
44
from django.urls import path
5+
from django.conf.urls.static import static
56

67

78
urlpatterns = [
@@ -14,3 +15,6 @@
1415
import debug_toolbar
1516

1617
urlpatterns = [path("__debug__", include(debug_toolbar.urls))] + urlpatterns
18+
19+
# We can't use `settings.MEDIA_URL` as the pattern since MEDIA_URL may be fully qualified
20+
urlpatterns += static("/media/", document_root=settings.MEDIA_ROOT)

pythonsd/migrations/0001_initial.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 3.2.25 on 2024-05-30 01:12
1+
# Generated by Django 3.2.25 on 2024-05-31 06:05
22

33
from django.db import migrations, models
44

@@ -18,7 +18,8 @@ class Migration(migrations.Migration):
1818
('name', models.CharField(max_length=255)),
1919
('meetup_url', models.URLField(blank=True, max_length=255)),
2020
('linkedin_url', models.URLField(blank=True, max_length=255)),
21-
('active', models.BooleanField(default=True)),
21+
('active', models.BooleanField(default=True, help_text='Set to False to hide this organizer from the organizers page')),
22+
('photo', models.ImageField(help_text='Recommended size of 400*400px or larger square', upload_to='organizers/')),
2223
],
2324
),
2425
]

pythonsd/models.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,16 @@ class Organizer(models.Model):
77
name = models.CharField(max_length=255)
88
meetup_url = models.URLField(max_length=255, blank=True)
99
linkedin_url = models.URLField(max_length=255, blank=True)
10-
active = models.BooleanField(default=True)
10+
active = models.BooleanField(
11+
default=True,
12+
help_text="Set to False to hide this organizer from the organizers page",
13+
)
14+
15+
# For production, store the image in Cloud Storage (S3, Appwrite, etc.)
16+
photo = models.ImageField(
17+
upload_to="organizers/",
18+
help_text="Recommended size of 400*400px or larger square",
19+
)
1120

1221
def __str__(self):
1322
return self.name
+36-8
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,48 @@
11
{% extends 'pythonsd/base.html' %}
22

33

4+
{% block title %}Organizers{% endblock title %}
5+
6+
47
{% block main %}
58
<div class="container mt-3">
69
<h1>Organizers</h1>
710

811
<p>If you would like to reach out, please contact the <a href="mailto:[email protected]">Python SD Organizers</a>.</p>
912

10-
{% for organizer in organizers %}
11-
<h5>{{ organizer.name }}</h5>
12-
<p><a href="{{ organizer.meetup_url }}" rel="nofollow noopener noreferrer" target="_blank">Meetup</a>
13-
{% if organizer.linkedin_url %}
14-
|&nbsp;<a href="{{ organizer.linkedin_url }}" rel="nofollow noopener noreferrer" target="_blank">LinkedIn</a>
15-
{% endif %}
16-
</p>
17-
{% endfor %}
13+
14+
{% if organizers %}
15+
<div class="row row-cols-2 row-cols-md-4">
16+
{% for organizer in organizers %}
17+
<div class="col mb-4">
18+
<div class="card">
19+
<img src="{{ organizer.photo.url }}" class="card-img-top" alt="{{ organizer.name }}">
20+
<div class="card-body">
21+
<h5 class="card-title">{{ organizer.name }}</h5>
22+
<p class="card-text">{{ organizer.description }}</p>
23+
<ul class="list-inline">
24+
{% if organizer.meetup_url %}
25+
<li class="list-inline-item">
26+
<a href="{{ organizer.meetup_url }}" rel="nofollow noopener noreferrer" target="_blank">
27+
<svg class="text-muted icon-1-5x" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path fill="currentColor" d="M99 414.3c1.1 5.7-2.3 11.1-8 12.3-5.4 1.1-10.9-2.3-12-8-1.1-5.4 2.3-11.1 7.7-12.3 5.4-1.2 11.1 2.3 12.3 8zm143.1 71.4c-6.3 4.6-8 13.4-3.7 20 4.6 6.6 13.4 8.3 20 3.7 6.3-4.6 8-13.4 3.4-20-4.2-6.5-13.1-8.3-19.7-3.7zm-86-462.3c6.3-1.4 10.3-7.7 8.9-14-1.1-6.6-7.4-10.6-13.7-9.1-6.3 1.4-10.3 7.7-9.1 14 1.4 6.6 7.6 10.6 13.9 9.1zM34.4 226.3c-10-6.9-23.7-4.3-30.6 6-6.9 10-4.3 24 5.7 30.9 10 7.1 23.7 4.6 30.6-5.7 6.9-10.4 4.3-24.1-5.7-31.2zm272-170.9c10.6-6.3 13.7-20 7.7-30.3-6.3-10.6-19.7-14-30-7.7s-13.7 20-7.4 30.6c6 10.3 19.4 13.7 29.7 7.4zm-191.1 58c7.7-5.4 9.4-16 4.3-23.7s-15.7-9.4-23.1-4.3c-7.7 5.4-9.4 16-4.3 23.7 5.1 7.8 15.6 9.5 23.1 4.3zm372.3 156c-7.4 1.7-12.3 9.1-10.6 16.9 1.4 7.4 8.9 12.3 16.3 10.6 7.4-1.4 12.3-8.9 10.6-16.6-1.5-7.4-8.9-12.3-16.3-10.9zm39.7-56.8c-1.1-5.7-6.6-9.1-12-8-5.7 1.1-9.1 6.9-8 12.6 1.1 5.4 6.6 9.1 12.3 8 5.4-1.5 9.1-6.9 7.7-12.6zM447 138.9c-8.6 6-10.6 17.7-4.9 26.3 5.7 8.6 17.4 10.6 26 4.9 8.3-6 10.3-17.7 4.6-26.3-5.7-8.7-17.4-10.9-25.7-4.9zm-6.3 139.4c26.3 43.1 15.1 100-26.3 129.1-17.4 12.3-37.1 17.7-56.9 17.1-12 47.1-69.4 64.6-105.1 32.6-1.1.9-2.6 1.7-3.7 2.9-39.1 27.1-92.3 17.4-119.4-22.3-9.7-14.3-14.6-30.6-15.1-46.9-65.4-10.9-90-94-41.1-139.7-28.3-46.9.6-107.4 53.4-114.9C151.6 70 234.1 38.6 290.1 82c67.4-22.3 136.3 29.4 130.9 101.1 41.1 12.6 52.8 66.9 19.7 95.2zm-70 74.3c-3.1-20.6-40.9-4.6-43.1-27.1-3.1-32 43.7-101.1 40-128-3.4-24-19.4-29.1-33.4-29.4-13.4-.3-16.9 2-21.4 4.6-2.9 1.7-6.6 4.9-11.7-.3-6.3-6-11.1-11.7-19.4-12.9-12.3-2-17.7 2-26.6 9.7-3.4 2.9-12 12.9-20 9.1-3.4-1.7-15.4-7.7-24-11.4-16.3-7.1-40 4.6-48.6 20-12.9 22.9-38 113.1-41.7 125.1-8.6 26.6 10.9 48.6 36.9 47.1 11.1-.6 18.3-4.6 25.4-17.4 4-7.4 41.7-107.7 44.6-112.6 2-3.4 8.9-8 14.6-5.1 5.7 3.1 6.9 9.4 6 15.1-1.1 9.7-28 70.9-28.9 77.7-3.4 22.9 26.9 26.6 38.6 4 3.7-7.1 45.7-92.6 49.4-98.3 4.3-6.3 7.4-8.3 11.7-8 3.1 0 8.3.9 7.1 10.9-1.4 9.4-35.1 72.3-38.9 87.7-4.6 20.6 6.6 41.4 24.9 50.6 11.4 5.7 62.5 15.7 58.5-11.1zm5.7 92.3c-10.3 7.4-12.9 22-5.7 32.6 7.1 10.6 21.4 13.1 32 6 10.6-7.4 13.1-22 6-32.6-7.4-10.6-21.7-13.5-32.3-6z"/></svg>
28+
</a>
29+
</li>
30+
{% endif %}
31+
{% if organizer.linkedin_url %}
32+
<li class="list-inline-item">
33+
<a href="{{ organizer.linkedin_url }}" rel="nofollow noopener noreferrer" target="_blank">
34+
<svg class="text-muted icon-1-5x" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path fill="currentColor" d="M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z"/></svg>
35+
</a>
36+
</li>
37+
{% endif %}
38+
</ul>
39+
</div>
40+
</div>
41+
</div>
42+
{% endfor %}
43+
</div>
44+
{% endif %}
45+
1846

1947
</div>
2048
{% endblock main %}

pythonsd/tests/__init__.py

+16
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from django import test
1212
from django.core.cache import cache
1313
from django.urls import reverse
14+
from django.core.files.uploadedfile import SimpleUploadedFile
1415
import responses
1516
import webtest
1617

@@ -20,6 +21,15 @@
2021
from ..models import Organizer
2122

2223

24+
# Bytes representing a valid 1-pixel PNG
25+
ONE_PIXEL_PNG_BYTES = (
26+
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00"
27+
b"\x01\x08\x04\x00\x00\x00\xb5\x1c\x0c\x02\x00\x00\x00\x0bIDATx"
28+
b"\x9cc\xfa\xcf\x00\x00\x02\x07\x01\x02\x9a\x1c1q\x00\x00\x00"
29+
b"\x00IEND\xaeB`\x82"
30+
)
31+
32+
2333
class TestBasicViews(test.TestCase):
2434
def test_code_of_conduct(self):
2535
response = self.client.get(reverse("code-of-conduct"))
@@ -53,13 +63,19 @@ def test_organizers(self):
5363
meetup_url="http://example.com/meetup",
5464
linkedin_url="http://example.com/linkedin",
5565
active=True,
66+
photo=SimpleUploadedFile(
67+
name="test.png", content=ONE_PIXEL_PNG_BYTES, content_type="image/png"
68+
),
5669
)
5770
org1.save()
5871

5972
org2 = Organizer(
6073
name="Second organizer",
6174
meetup_url="http://example.com/meetup",
6275
active=False,
76+
photo=SimpleUploadedFile(
77+
name="test.png", content=ONE_PIXEL_PNG_BYTES, content_type="image/png"
78+
),
6379
)
6480
org2.save()
6581

pythonsd/tests/test_models.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,36 @@
11
from django import test
2+
from django.core.files.uploadedfile import SimpleUploadedFile
23

34
from ..models import Organizer
45

56

7+
# Bytes representing a valid 1-pixel PNG
8+
ONE_PIXEL_PNG_BYTES = (
9+
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00"
10+
b"\x01\x08\x04\x00\x00\x00\xb5\x1c\x0c\x02\x00\x00\x00\x0bIDATx"
11+
b"\x9cc\xfa\xcf\x00\x00\x02\x07\x01\x02\x9a\x1c1q\x00\x00\x00"
12+
b"\x00IEND\xaeB`\x82"
13+
)
14+
15+
616
class TestOrganizer(test.TestCase):
717
def setUp(self):
818
self.org1 = Organizer(
919
name="First organizer",
1020
meetup_url="http://example.com/meetup",
1121
linkedin_url="http://example.com/linkedin",
22+
photo=SimpleUploadedFile(
23+
name="test.png", content=ONE_PIXEL_PNG_BYTES, content_type="image/png"
24+
),
1225
)
1326
self.org1.save()
1427

1528
self.org2 = Organizer(
16-
name="Second organizer", meetup_url="http://example.com/meetup"
29+
name="Second organizer",
30+
meetup_url="http://example.com/meetup",
31+
photo=SimpleUploadedFile(
32+
name="test.png", content=ONE_PIXEL_PNG_BYTES, content_type="image/png"
33+
),
1734
)
1835
self.org2.save()
1936

requirements/common.txt

+3
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,6 @@ django-enforce-host==1.1.0
2020

2121
# For parsing YouTube's XML feed
2222
defusedxml==0.7.1
23+
24+
# Used for image field handling
25+
pillow==10.3.0

0 commit comments

Comments
 (0)