Skip to content

Commit 53ff9ed

Browse files
committed
Add ability to upload images to tools via markdownx drag interface.
1 parent ac7a84f commit 53ff9ed

File tree

16 files changed

+307
-938
lines changed

16 files changed

+307
-938
lines changed

poetry.lock

Lines changed: 0 additions & 912 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ gunicorn = "^19.9"
2727
whitenoise = "^4.1"
2828
django-allauth = { git = "https://github.com/pennersr/django-allauth.git", commit = "f70cb3d" }
2929
#django-allauth = "^0.39.1"
30+
django-imagekit = "^4.0"
31+
pillow = "^5.4"
3032

3133
[tool.poetry.dev-dependencies]
3234
ipdb = "^0.12"

toolhub/settings/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"qr_code",
3232
"memoize",
3333
"tagulous",
34+
"imagekit",
3435
# Toolhub
3536
"borrowing.apps.BorrowingConfig",
3637
"toolhub",
@@ -169,3 +170,5 @@
169170
"markdown.extensions.toc",
170171
"markdown.extensions.wikilinks",
171172
]
173+
174+
MARKDOWNX_UPLOAD_CONTENT_TYPES = ("image/jpeg", "image/png")

toolhub/static/main.css

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,17 @@ body, #main-wrapper {
1212
.socialaccount_provider {
1313
font-size: 1.5rem;
1414
}
15+
16+
.card-tool-img {
17+
max-height: 120px;
18+
overflow: hidden;
19+
}
20+
21+
.card-tool-img img {
22+
width: 100%;
23+
height: auto;
24+
}
25+
26+
.content img {
27+
max-width: 100%;
28+
}

toolhub/templates/layouts/base.jinja

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<title>Toolhub - {% block head_title %}{% endblock %}</title>
99
<!-- BS4 CDN for now. -->
1010
{% block head_css %}
11-
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
11+
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
1212
{% block main_css %}<link rel="stylesheet" href="{{ static('main.css') }}">{% endblock %}
1313
<link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.6.3/css/all.css" integrity="sha384-LRlmVvLKVApDVGuspQFnRQJjkv0P7/YFrw84YYQtmYG4nK8c+M+NlmYDCv0rKWpG" crossorigin="anonymous">
1414
{% endblock %}
@@ -41,9 +41,9 @@
4141
</section>
4242
{% block body_javascript %}
4343
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
44-
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
45-
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
46-
{% endblock %}
44+
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
45+
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
46+
{% endblock %}
4747
{% endblock %}
4848
</body>
4949
</html>

toolhub/templates/layouts/sub_header_base.jinja

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<div class="container">
88
{% block page_header %}
99
{#- you can use page_header to place header based navigation -#}
10-
<h3 class="mb-0 text-light">{% block sub_header %}Change Me{% endblock %}</h3>
10+
<h3 class="mb-0 text-light mr-auto">{% block sub_header %}Change Me{% endblock %}</h3>
1111
{% endblock %}
1212
{% block sub_actions %}{% endblock %}
1313
</div>

toolhub/templates/macros/tool_card.jinja

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
<div {{ classes }}>
1313
{% set photo = tool.cover_photo %}
1414
{% if photo %}
15-
<img class="card-img-top" src="{{ photo.file.file }}" alt="{{ photo.title|default('tool photo') }}">
15+
<div class="card-img-top card-tool-img">
16+
<img src="{{ photo.card_thumbnail.url }}" alt="{{ photo.title|default('tool photo') }}">
17+
</div>
1618
{% endif %}
1719
<div class="card-body">
1820
<h5 class="card-title"><a href="{{ tool_link }}">{{ tool.title }}</a> </h5>

tools/forms.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
from braces.forms import UserKwargModelFormMixin
12
from crispy_forms.layout import Button, Div, Field, Fieldset, Reset, Submit
23
from django import forms
4+
from django.urls import reverse
35
from django.utils.translation import ugettext_lazy as _
46

57
from toolhub_auth.models import User
6-
from tools.models import ClearancePermission, UserTool
8+
from tools.models import ClearancePermission, ToolPhoto, UserTool
79
from utils.forms import CrispyFormMixin
810

911

@@ -19,7 +21,9 @@ def layout_args(self, helper):
1921
Fieldset(
2022
None,
2123
Field("title"),
22-
Field("description", css_class="h-100", label_class="", field_class=""),
24+
# attempts
25+
# css_class="h-100", label_class="", field_class="", help_text="test"
26+
Field("description"),
2327
Field("taxonomies"),
2428
Field("visibility"),
2529
Field("clearance"),
@@ -34,6 +38,24 @@ class Meta:
3438
fields = ("title", "description", "taxonomies", "visibility", "clearance")
3539
model = UserTool
3640

41+
def post_super_init(self):
42+
super().post_super_init()
43+
self.update_markdown_upload_path()
44+
45+
def update_markdown_upload_path(self):
46+
if hasattr(self, "instance") and self.pk_field:
47+
reverse_kwargs = {self.pk_field: self.instance.pk}
48+
else:
49+
reverse_kwargs = {}
50+
attrs = self.fields["description"].widget.attrs
51+
attrs.update(
52+
{
53+
"data-markdownx-upload-urls-path": reverse(
54+
"tools:upload_tool_photo", kwargs=reverse_kwargs
55+
)
56+
}
57+
)
58+
3759

3860
class UserToolUpdateForm(UserToolCreateForm):
3961
action_button_label = _("Update tool")
@@ -116,3 +138,27 @@ def save(self):
116138
)
117139
removed_users = init_users - edited_users
118140
self.instance.permissions.filter(cleared_user_id__in=removed_users).delete()
141+
142+
143+
class UploadToolPhotoForm(UserKwargModelFormMixin, forms.ModelForm):
144+
"""
145+
Optionally takes a tool to make association
146+
Also set the `uploading_user`?
147+
"""
148+
149+
class Meta:
150+
model = ToolPhoto
151+
fields = ("file",)
152+
153+
def __init__(self, *args, **kwargs):
154+
# move file uploaded to what the form expects
155+
kwargs["files"]["file"] = kwargs["files"]["image"]
156+
self.tool = kwargs.pop("tool", None)
157+
return super().__init__(*args, **kwargs)
158+
159+
def save(self, commit=True):
160+
# This will set the photo tool to null if none was passed
161+
self.instance.tool = self.tool
162+
self.instance.uploading_user = self.user
163+
# NOTE: get title from file name temporarily
164+
return super().save(commit=commit)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Generated by Django 2.1.7 on 2019-03-12 06:26
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('tools', '0007_auto_20181106_1734'),
11+
]
12+
13+
operations = [
14+
migrations.AlterField(
15+
model_name='toolphoto',
16+
name='file',
17+
field=models.ImageField(upload_to='%Y/%m/'),
18+
),
19+
migrations.AlterField(
20+
model_name='toolphoto',
21+
name='tool',
22+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='photos', to='tools.UserTool'),
23+
),
24+
]

tools/models.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@
1515
from django.urls import reverse
1616
from django.utils.translation import ugettext_lazy as _
1717
from django_extensions.db.models import TimeStampedModel, TitleDescriptionModel
18+
from imagekit import ImageSpec
19+
from imagekit.models import ImageSpecField
20+
from imagekit.processors import ResizeToFill, ResizeToFit
1821
from markdownx.models import MarkdownxField
1922
from tagulous.models import TagField, TagTreeModel
2023

21-
from utils.models import StateMachineMixin
22-
2324
from tools.exceptions import ToolAvailabilityException, ToolClearanceException
2425
from tools.querysets import ToolHistoryQuerySet, UserToolQuerySet
26+
from utils.models import StateMachineMixin
2527

2628

2729
class ToolTaxonomy(TagTreeModel):
@@ -228,14 +230,38 @@ def __str__(self):
228230
return f"{self.cleared_by_user} cleared {self.cleared_user} ({self.tool})"
229231

230232

233+
class ToolCardThumbnailSpec(ImageSpec):
234+
processors = [ResizeToFill(320, 160)]
235+
format = "JPEG"
236+
options = {"quality": 80}
237+
238+
239+
class ToolGalleryThumbnailSpec(ToolCardThumbnailSpec):
240+
processors = [ResizeToFill(120, 120)]
241+
242+
243+
class ToolContentThumbnailSpec(ToolCardThumbnailSpec):
244+
processors = [ResizeToFit(825, 620)]
245+
246+
231247
class ToolPhoto(TimeStampedModel):
232-
tool = models.ForeignKey(UserTool, on_delete=models.CASCADE, related_name="photos")
248+
tool = models.ForeignKey(
249+
UserTool, on_delete=models.CASCADE, related_name="photos", blank=True, null=True
250+
)
233251
uploading_user = models.ForeignKey(
234252
settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="uploaded_photos"
235253
)
236-
file = models.FileField()
254+
file = models.ImageField(upload_to="%Y/%m/")
237255
title = models.CharField(max_length=255, blank=True)
238256

257+
# Thumbnails
258+
card_thumbnail = ImageSpecField(source="file", spec=ToolCardThumbnailSpec)
259+
gallery_thumbnail = ImageSpecField(source="file", spec=ToolGalleryThumbnailSpec)
260+
content_thumbnail = ImageSpecField(source="file", spec=ToolContentThumbnailSpec)
261+
239262
class Meta:
240263
ordering = ("-created",)
241264
get_latest_by = "created"
265+
266+
def __str__(self):
267+
return f"{self.tool} - {self.title}"

0 commit comments

Comments
 (0)