18 Commits

Author SHA1 Message Date
e283449e0d German language file 2022-12-29 21:31:51 +01:00
e483084846 Fitting Templates with translate tag 2022-12-29 21:31:11 +01:00
c38ab9a9b4 Upload image to gallery and view images through galleries 2022-12-28 15:57:52 +01:00
582155bdd6 Funny name generation 2022-12-27 22:09:26 +01:00
651218c48b List Images and Galleries, Access restrictions 2022-12-27 22:09:10 +01:00
a54ad8954c View for "my images" 2022-12-25 13:49:44 +01:00
1df19b8a74 Assigning Visitors to Sessions and Images 2022-12-25 12:20:38 +01:00
8c06ce09fa Representation of an image in the admin view 2022-12-25 12:20:13 +01:00
b0d9c46eb2 #2 Handling image upload 2022-12-25 12:19:54 +01:00
0f69c44cc3 #3 CSS and HTML 2022-12-25 12:18:15 +01:00
ac26050a78 Merge branch 'feature/image-upload' into develop 2022-12-25 12:15:57 +01:00
c5dfb4f926 Squashed commit of the following:
commit 4c5a5fee8d
Author: reverend <reverend@reverend2048.de>
Date:   Sun Dec 18 09:24:15 2022 +0100

commit 17647bf4b7
Author: reverend <reverend@reverend2048.de>
Date:   Sun Dec 18 09:23:36 2022 +0100

commit 4deeb3d773
Author: reverend <reverend@reverend2048.de>
Date:   Sun Dec 18 09:23:06 2022 +0100

commit 5342e62bcb
Author: reverend <reverend@reverend2048.de>
Date:   Sun Dec 18 09:22:44 2022 +0100

commit 02512c1677
Author: reverend <reverend@reverend2048.de>
Date:   Sun Dec 18 09:21:57 2022 +0100
2022-12-18 09:24:55 +01:00
4c5a5fee8d #3 Displaying Models on Admin page 2022-12-18 09:24:15 +01:00
17647bf4b7 #3 Setting up Views for Uploading and listing images 2022-12-18 09:23:36 +01:00
4deeb3d773 #3 Upload form for Images 2022-12-18 09:23:06 +01:00
5342e62bcb #3 Setting up Media and Static root 2022-12-18 09:22:44 +01:00
02512c1677 #3 Dependencies for Images 2022-12-18 09:21:57 +01:00
ba6a40fbe5 Migration for Base Model 2022-12-18 09:20:30 +01:00
26 changed files with 1241 additions and 14 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
django_web_galleries/media/*
django_web_galleries/static/*

View File

@@ -5,8 +5,10 @@ name = "pypi"
[packages] [packages]
django = "*" django = "*"
django-responsive-images = "*"
[dev-packages] pillow = "*"
django-widget-tweaks = "*"
names = "*"
[requires] [requires]
python_version = "3.8" python_version = "3.8"

View File

@@ -37,6 +37,9 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'responsive_images',
'widget_tweaks',
'web_galleries',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@@ -115,9 +118,13 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.1/howto/static-files/ # https://docs.djangoproject.com/en/4.1/howto/static-files/
STATIC_ROOT = BASE_DIR / 'static'
STATIC_URL = 'static/' STATIC_URL = 'static/'
# Default primary key field type # Default primary key field type
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
MEDIA_ROOT = BASE_DIR / 'media'
MEDIA_URL = 'media/'

View File

@@ -14,8 +14,22 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.contrib import admin from django.contrib import admin
from django.urls import path from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('', include('web_galleries.urls'))
] ]
if settings.DEBUG:
urlpatterns += static(
settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT
)
urlpatterns += static(
settings.STATIC_URL,
document_root=settings.STATIC_ROOT
)

View File

@@ -1,3 +1,11 @@
from django.contrib import admin from django.contrib import admin
# Register your models here. from .models import (
Image,
Visitor,
Gallery
)
admin.site.register(Image)
admin.site.register(Visitor)
admin.site.register(Gallery)

View File

@@ -0,0 +1,38 @@
from django import forms
from django.utils.translation import gettext as _
from .models import (
Image,
Gallery
)
class ImageUploadForm(forms.ModelForm):
class Meta:
model = Image
fields = ['description', 'image_file', 'private', 'title']
labels = {
'private': 'Make this image private'
}
class GalleryCreatingForm(forms.ModelForm):
class Meta:
model = Gallery
fields = ['title', 'private']
labels = {
'private': 'Make this gallery private'
}
images = forms.ImageField(
widget=forms.ClearableFileInput(attrs={'multiple': True})
)
class AccessCodeForm(forms.Form):
access_code = forms.CharField(max_length=32)
class VisitorNameForm(forms.Form):
name = forms.CharField(max_length=50)
class ImageUploadForm(forms.Form):
images = forms.ImageField(
widget=forms.ClearableFileInput(attrs={'multiple': True})
)

View File

@@ -0,0 +1,109 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-12-28 15:24+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: web_galleries/templates/gallery/create_gallery.html:14
msgid "Create"
msgstr "Erstellen"
#: web_galleries/templates/gallery/gallery_detail.html:10
msgid "Upload image to this gallery"
msgstr "Bild in diese Gallerie hochladen"
#: web_galleries/templates/gallery/gallery_detail.html:21
msgid "You have gained access to the following people"
msgstr "Du hast folgenden Leuten Zugriff erteilt"
#: web_galleries/templates/gallery/gallery_detail.html:23
msgid "Revoke access"
msgstr "Zugriff entziehen"
#: web_galleries/templates/gallery/gallery_no_access.html:9
msgid "You do not have access to this private gallery"
msgstr "Du hast keinen Zugriff auf diese privaten Gallerie"
#: web_galleries/templates/gallery/gallery_no_access.html:10
msgid "But you can gain access by entering the access code below:"
msgstr "Du kannst aber Zugriff erhalten indem du den Zugangscode eingibst:"
#: web_galleries/templates/gallery/gallery_no_access.html:16
msgid "Enter"
msgstr "Abschicken"
#: web_galleries/templates/gallery/list_galleries.html:12
#: web_galleries/templates/gallery/my_galleries.html:14
#: web_galleries/templates/gallery/my_galleries.html:51
msgid "no images"
msgstr "keine Bilder"
#: web_galleries/templates/gallery/my_galleries.html:9
msgid "Your galleries "
msgstr "Deine Gallerien"
#: web_galleries/templates/gallery/my_galleries.html:46
msgid "Galleries you were invited to"
msgstr "Gallerien in die du eingeladen wurdest"
#: web_galleries/templates/gallery/upload_image.html:8
msgid "Upload image to"
msgstr "Bild hochladen in"
#: web_galleries/templates/gallery/upload_image.html:13
#: web_galleries/templates/upload_image/upload.html:16
msgid "Upload"
msgstr "Hochladen"
#: web_galleries/templates/global.html:22
msgid "Home"
msgstr "Startseite"
#: web_galleries/templates/global.html:27
msgid "Upload image"
msgstr "Bild hochladen"
#: web_galleries/templates/global.html:32
msgid "My images"
msgstr "Meine Bilder"
#: web_galleries/templates/global.html:37
msgid "My galleries"
msgstr "Meine Gallerien"
#: web_galleries/templates/global.html:42
msgid "Create gallery"
msgstr "Gallerie erstellen"
#: web_galleries/templates/global.html:48
msgid "Hello there"
msgstr "Hallo"
#: web_galleries/templates/global.html:53
msgid "My Profile"
msgstr "Dein Profil"
#: web_galleries/templates/image/my_images.html:9
msgid "Images you uploaded"
msgstr "Bilder die du hochgeladen hast"
#: web_galleries/templates/image/my_images.html:21
msgid "Images shared with you"
msgstr "Bilder die mit dir geteilt wurden"
#: web_galleries/templates/visitor/visitor_settings.html:12
msgid "Save"
msgstr "Speichern"

View File

@@ -0,0 +1,55 @@
# Generated by Django 4.1.4 on 2022-12-18 08:19
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Gallery',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=100, null=True, verbose_name='Optional title for this gallery')),
('private', models.BooleanField(blank=True, default=False, verbose_name='Wether the gallery is vsibile publicly or to certain users only')),
('access_code', models.CharField(max_length=32, null=True, verbose_name='Code to access private galleries')),
],
),
migrations.CreateModel(
name='Visitor',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('session_id', models.CharField(max_length=50, verbose_name='A cookie set by this application to identify a visitor by session')),
('name', models.CharField(default=None, max_length=50, null=True, unique=True, verbose_name='Human readable, self assigned name of the visitor')),
],
),
migrations.CreateModel(
name='Image',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('description', models.TextField(null=True, verbose_name='An optional description of the Image')),
('image_file', models.ImageField(upload_to='web_galleries/uploads/', verbose_name='File of the image')),
('private', models.BooleanField(blank=True, default=False, verbose_name='Wether this image is visible publicly or to certain users only')),
('access_code', models.CharField(max_length=32, null=True, verbose_name='Code to access private images')),
('uploaded_when', models.DateTimeField(auto_now_add=True, verbose_name='When this picture was uploaded')),
('galleries', models.ManyToManyField(related_name='images', to='web_galleries.gallery', verbose_name='What galleries this image is in')),
('uploaded_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='uploaded_images', to='web_galleries.visitor', verbose_name='Which visitor uploaded this picture')),
],
),
migrations.AddField(
model_name='gallery',
name='created_by',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='created_galleries', to='web_galleries.visitor', verbose_name='Visitor who created the gallery'),
),
migrations.AddField(
model_name='gallery',
name='visitors',
field=models.ManyToManyField(related_name='linked_galleries', to='web_galleries.visitor', verbose_name='Visitors that a part of this gallery'),
),
]

View File

@@ -1,7 +1,15 @@
import uuid
import names
from django.db import models from django.db import models
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
def get_uuid():
return str(uuid.uuid4())
def get_visitor_name():
return names.get_first_name()
class Visitor(models.Model): class Visitor(models.Model):
""" """
Stores information about a visitor of this application. Stores information about a visitor of this application.
@@ -10,16 +18,20 @@ class Visitor(models.Model):
""" """
session_id = models.CharField( session_id = models.CharField(
_('A cookie set by this application to identify a visitor by session'), _('A cookie set by this application to identify a visitor by session'),
max_length=50 max_length=50,
default=get_uuid
) )
name = models.CharField( name = models.CharField(
_('Human readable, self assigned name of the visitor'), _('Human readable, self assigned name of the visitor'),
null=True, null=True,
default=None, default=get_visitor_name,
max_length=50, max_length=50,
unique=True unique=True
) )
def __str__(self):
return self.name
class Gallery(models.Model): class Gallery(models.Model):
""" """
@@ -39,6 +51,7 @@ class Gallery(models.Model):
access_code = models.CharField( access_code = models.CharField(
_('Code to access private galleries'), _('Code to access private galleries'),
null=True, null=True,
blank=True,
max_length=32 max_length=32
) )
@@ -51,22 +64,26 @@ class Gallery(models.Model):
visitors = models.ManyToManyField( visitors = models.ManyToManyField(
Visitor, Visitor,
blank=True,
verbose_name=_('Visitors that a part of this gallery'), verbose_name=_('Visitors that a part of this gallery'),
related_name='linked_galleries' related_name='linked_galleries'
) )
def clean(self): def __str__(self):
if self.private and access_code is None: return self.title
raise ValidationErrorr('Private gallery needs an access code')
if len(self.visitors.all()) > 10:
raise ValidationError('Gallery can not have more than 10 visitors assigned')
class Image(models.Model): class Image(models.Model):
""" """
An image contains the path to the image file and several information An image contains the path to the image file and several information
like description, uploader and affiliation to galleries. like description, uploader and affiliation to galleries.
""" """
title = models.CharField(
_('Title of the image'),
max_length=100,
null=True,
blank=True
)
description = models.TextField( description = models.TextField(
_('An optional description of the Image'), _('An optional description of the Image'),
null=True null=True
@@ -84,6 +101,7 @@ class Image(models.Model):
access_code = models.CharField( access_code = models.CharField(
_('Code to access private images'), _('Code to access private images'),
null=True, null=True,
blank=True,
max_length=32 max_length=32
) )
@@ -101,7 +119,24 @@ class Image(models.Model):
galleries = models.ManyToManyField( galleries = models.ManyToManyField(
Gallery, Gallery,
blank=True,
verbose_name=_('What galleries this image is in'), verbose_name=_('What galleries this image is in'),
related_name='images' related_name='images'
) )
visible_to = models.ManyToManyField(
Visitor,
blank=True,
verbose_name=_('Visitors that can see this picture if it is marked private'),
related_name='visible_images'
)
def __str__(self):
if self.title:
return self.title
elif self.description:
return self.description[:100]
else:
return super().__str__()

View File

@@ -0,0 +1,95 @@
.RV-Header {
height: 80px;
margin-bottom: 70px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
border-bottom: 1px solid gray;
}
.RV-Navigation__list {
display: flex;
flex-direction: row;
list-style-type: none;
gap: 1em;
font-size: 22px;
}
.RV-Navigation__link {
text-decoration: none;
display: inline-block;
transition: transform 300ms ease-in-out;
}
.RV-Navigation__link:hover {
transform: scale(1.2);
}
.RV-Images__list {
list-style-type: none;
display: grid;
gap: 22px;
grid-template-columns: repeat(auto-fit, 200px);
}
.RV-Image__link {
display: flex;
flex-direction: row;
flex-wrap: wrap;
transition: transform 300ms ease-in-out;
}
.RV-Image__link:hover {
transform: scale(1.1);
}
.RV-Image__title {
flex-basis: 100%;
display: block;
}
.RV-Image {
max-width: 100%;
}
.RV-Fieldset {
margin: 50px 200px;
display: flex;
flex-direction: column;
gap: 30px;
}
.RV-Navigation {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
}
.RV-Input {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.RV-Input--compact {
flex-direction: row;
}
.RV-Input.RV-Input--reverse {
flex-direction: column-reverse;
align-items: flex-end;
}
.RV-Input--compact.RV-Input--reverse {
flex-direction: row-reverse;
justify-content: flex-end;
}

View File

@@ -0,0 +1,17 @@
{% extends '../global.html' %}
{% load i18n %}
{% block content %}
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="RV-Fieldset">
{% include 'partials/form_input.html' with field=form.title %}
{% include 'partials/form_input.html' with field=form.private classes='RV-Input--compact RV-Input--reverse' %}
{% include 'partials/form_input.html' with field=form.images %}
</div>
<div class="RV-Fieldset">
<button type="submit">{% translate 'Create' %}</button>
</div>
</form>
{% endblock content %}

View File

@@ -0,0 +1,28 @@
{% extends '../global.html' %}
{% load i18n %}
{% load responsive_images %}
{% block content %}
{% if gallery.title %}
<h1>{{gallery.title}}</h1>
{% endif %}
<a href="{% url 'gallery_upload' gallery_id=gallery.id %}">{% translate 'Upload image to this gallery' %}</a>
<section class="RV-Images">
<li class="RV-Images__list">
{% for image in gallery.images.all %}
<a href="{% url 'image_through_gallery' gallery_id=gallery.id image_id=image.id %}" class="RV-Image__link RV-Image__link--detail">
<img class="RV-Image__source" src="{% src image.image_file 200x200 %}">
</a>
{% endfor %}
</li>
{% if gallery.private and gallery.visitors.all|length > 0 and gallery.created_by == visitor %}
<div>
<h2>{% translate 'You have gained access to the following people' %}</h2>
{% for visitor in gallery.visitors.all %}
{{visitor.name}} <a href="{% url 'gallery_revoke_access' gallery_id=gallery.id visitor_id=visitor.id %}">{% translate 'Revoke access' %}</a>
{% endfor %}
</div>
{% endif %}
</section>
{% endblock content %}

View File

@@ -0,0 +1,19 @@
{% extends '../global.html' %}
{% load i18n %}
{% block content %}
<form method="POST">
{% csrf_token %}
<div class="RV-Fieldset">
<h1>{% translate 'You do not have access to this private gallery' %}</h1>
{% translate 'But you can gain access by entering the access code below:' %}'
</div>
<div class="RV-Fieldset">
{% include 'partials/form_input.html' with field=form.access_code %}
</div>
<div class="RV-Fieldset">
<button type="submit">{% translate 'Enter' %}</button>
</div>
</form>
{% endblock content %}

View File

@@ -0,0 +1,41 @@
{% extends '../global.html' %}
{% load i18n %}
{% load responsive_images %}
{% block content %}
<section class="RV-Images">
<li class="RV-Images__list">
{% for gallery in galleries %}
<a href="{% url 'gallery' pk=gallery.pk %}" class="RV-Image__link RV-Image__link--detail">
{% if gallery.images.all|length <= 0 %}
{% translate 'no images' %}
{% endif %}
{% if gallery.images.all|length == 1 %}
{% for image in gallery.images.all %}
<img class="RV-Image__source" src="{% src image.image_file 200x200 %}">
{% endfor %}
{% endif %}
{% if gallery.images.all|length == 2 %}
{% for image in gallery.images.all %}
<img class="RV-Image__source" src="{% src image.image_file 100x200 %}">
{% endfor %}
{% endif %}
{% if gallery.images.all|length == 3 %}
{% for image in gallery.images.all %}
<img class="RV-Image__source" src="{% src image.image_file 100x100 %}">
{% endfor %}
{% endif %}
{% if gallery.images.all|length >= 4 %}
{% for image in gallery.images.all|slice:":4" %}
<img class="RV-Image__source" src="{% src image.image_file 100x100 %}">
{% endfor %}
{% endif %}
{% if gallery.title %}
<p class="RV-Image__title">{{gallery.title}}</p>
{% endif %}
</a>
{% endfor %}
</li>
</section>
{% endblock content %}

View File

@@ -0,0 +1,81 @@
{% extends '../global.html' %}
{% load i18n %}
{% load responsive_images %}
{% block content %}
{% if created_galleries|length > 0 %}
<section class="RV-Images">
<h2>{% translate 'Your galleries '%}</h2>
<li class="RV-Images__list">
{% for gallery in created_galleries %}
<a href="{% url 'gallery' pk=gallery.pk %}" class="RV-Image__link RV-Image__link--detail">
{% if gallery.images.all|length <= 0 %}
{% translate 'no images' %}
{% endif %}
{% if gallery.images.all|length == 1 %}
{% for image in gallery.images.all %}
<img class="RV-Image__source" src="{% src image.image_file 200x200 %}">
{% endfor %}
{% endif %}
{% if gallery.images.all|length == 2 %}
{% for image in gallery.images.all %}
<img class="RV-Image__source" src="{% src image.image_file 100x200 %}">
{% endfor %}
{% endif %}
{% if gallery.images.all|length == 3 %}
{% for image in gallery.images.all %}
<img class="RV-Image__source" src="{% src image.image_file 100x100 %}">
{% endfor %}
{% endif %}
{% if gallery.images.all|length >= 4 %}
{% for image in gallery.images.all|slice:":4" %}
<img class="RV-Image__source" src="{% src image.image_file 100x100 %}">
{% endfor %}
{% endif %}
{% if gallery.title %}
<p class="RV-Image__title">{{gallery.title}}</p>
{% endif %}
</a>
{% endfor %}
</li>
</section>
{% endif %}
{% if invited_galleries|length > 0 %}
<section class="RV-Images">
<h2>{% translate 'Galleries you were invited to' %}</h2>
<li class="RV-Images__list">
{% for gallery in invited_galleries %}
<a href="{% url 'gallery' pk=gallery.pk %}" class="RV-Image__link RV-Image__link--detail">
{% if gallery.images.all|length <= 0 %}
{% translate 'no images' %}
{% endif %}
{% if gallery.images.all|length == 1 %}
{% for image in gallery.images.all %}
<img class="RV-Image__source" src="{% src image.image_file 200x200 %}">
{% endfor %}
{% endif %}
{% if gallery.images.all|length == 2 %}
{% for image in gallery.images.all %}
<img class="RV-Image__source" src="{% src image.image_file 100x200 %}">
{% endfor %}
{% endif %}
{% if gallery.images.all|length == 3 %}
{% for image in gallery.images.all %}
<img class="RV-Image__source" src="{% src image.image_file 100x100 %}">
{% endfor %}
{% endif %}
{% if gallery.images.all|length >= 4 %}
{% for image in gallery.images.all|slice:":4" %}
<img class="RV-Image__source" src="{% src image.image_file 100x100 %}">
{% endfor %}
{% endif %}
{% if gallery.title %}
<p class="RV-Image__title">{{gallery.title}}</p>
{% endif %}
</a>
{% endfor %}
</li>
</section>
{% endif %}
{% endblock content %}

View File

@@ -0,0 +1,16 @@
{% extends '../global.html' %}
{% load i18n %}
{% block content %}
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<h2>{% translate 'Upload image to' %} "{{gallery.title}}"</h2>
<div class="RV-Fieldset">
{% include 'partials/form_input.html' with field=form.images %}
</div>
<div class="RV-Fieldset">
<button type="submit">{% translate 'Upload' %}</button>
</div>
</form>
{% endblock content %}

View File

@@ -0,0 +1,73 @@
{% load static %}
{% load i18n %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Gallery</title>
<link rel="stylesheet" href="{% static 'bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'web-galleries.css' %}">
</head>
<body>
<div class="RV-Page">
<header class="RV-Header">
<nav class="RV-Navigation">
<ul class="RV-Navigation__list">
<li class="RV-Navigation__item">
<a href="{% url 'home' %}" class="RV-Navigation__link">
{% translate 'Home' %}
</a>
</li>
<li class="RV-Navigation__item">
<a href="{% url 'upload_image' %}" class="RV-Navigation__link">
{% translate 'Upload image' %}
</a>
</li>
<li class="RV-Navigation__item">
<a href="{% url 'my_images' %}" class="RV-Navigation__link">
{% translate 'My images' %}
</a>
</li>
<li class="RV-Navigation__item">
<a href="{% url 'my_galleries' %}" class="RV-Navigation__link">
{% translate 'My galleries' %}
</a>
</li>
<li class="RV-Navigation__item">
<a href="{% url 'create_gallery' %}" class="RV-Navigation__link">
{% translate 'Create gallery' %}
</a>
</li>
</ul>
<div class="RV-UserInfo">
<div class="RV-UserInfo__name">
{% translate 'Hello there' %}, {{ visitor.name }}
</div>
<ul class="RV-UserInfo__links">
<li class="RV-UserInfo__link">
<a href="{% url 'visitor_profile' %}">
{% translate 'My Profile' %}
</a>
</li>
</ul>
</div>
</nav>
</header>
{% if request.META.HTTP_REFERER %}
<a href="{{ request.META.HTTP_REFERER }}">back</a>
{% endif %}
<main class="RV-Content">
{% block content %}
{% endblock content %}
</main>
<footer class="RV-Footer">
</footer>
</div>
</body>
</html>

View File

@@ -0,0 +1,7 @@
{% extends '../global.html' %}
{% load responsive_images %}
{% block content %}
<img class="RV-Image" src="{% src image.image_file 1920x1200 nocrop %}" />
{% endblock content %}

View File

@@ -0,0 +1,7 @@
{% extends '../global.html' %}
{% block content %}
<h1>You have no access to this private image</h1>
But you can gain access by entering the access code below:
{% endlbock content %}

View File

@@ -0,0 +1,31 @@
{% extends '../global.html' %}
{% load i18n %}
{% load responsive_images %}
{% block content %}
{% if uploaded_images|length > 0 %}
<section class="RV-Images">
<h2>{% translate 'Images you uploaded' %}</h2>
<li class="RV-Images__list">
{% for image in uploaded_images %}
<a href="{% url 'image' pk=image.id %}" class="RV-Image__link RV-Image__link--detail">
<img class="RV-Image__source" src="{% src image.image_file 200x200 %}">
</a>
{% endfor %}
</li>
</section>
{% endif %}
{% if shared_images|length > 0 %}
<section class="RV-Images">
<h2>{% translate 'Images shared with you' %}</h2>
<li class="RV-Images__list">
{% for image in shared_images %}
<a href="{% url 'image' pk=image.id %}" class="RV-Image__link RV-Image__link--detail">
<img class="RV-Image__source" src="{% src image.image_file 200x200 %}">
</a>
{% endfor %}
</li>
</section>
{% endif %}
{% endblock content %}

View File

@@ -0,0 +1,15 @@
{% extends '../global.html' %}
{% load responsive_images %}
{% block content %}
<section class="RV-Images">
<li class="RV-Images__list">
{% for image in images %}
<a href="{% url 'image' pk=image.id %}" class="RV-Image__link RV-Image__link--detail">
<img class="RV-Image__source" src="{% src image.image_file 200x200 %}">
</a>
{% endfor %}
</li>
</section>
{% endblock content %}

View File

@@ -0,0 +1,16 @@
{% load widget_tweaks %}
<div class="RV-Input {% if classes%}{{classes}}{% endif %} {% if field.errors %} RV-Input--error {% endif %}">
<label for="{{field.id_for_label}}" class="RV-Input__Label">{{field.label}}</label>
{% render_field field class+='RV-Input__Field' %}
<span class="RV-Input__Message">
{% if field.errors %}
{% for error in field.errors%}
{{error}}
{% endfor %}
{% elif field.help_text%}
{{ field.help_text }}
{% endif %}
</span>
</div>

View File

@@ -0,0 +1,19 @@
{% extends '../global.html' %}
{% load i18n %}
{% block content %}
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="RV-Fieldset">
{% include 'partials/form_input.html' with field=form.title %}
{% include 'partials/form_input.html' with field=form.image_file %}
{% include 'partials/form_input.html' with field=form.description %}
{% include 'partials/form_input.html' with field=form.private classes='RV-Input--compact RV-Input--reverse' %}
</div>
<div class="RV-Fieldset">
<button type="submit">{% translate 'Upload' %}</button>
</div>
</form>
{% endblock content %}

View File

@@ -0,0 +1,15 @@
{% extends '../global.html' %}
{% load i18n %}
{% block content %}
<form method="POST">
{% csrf_token %}
<div class="RV-Fieldset">
{% include 'partials/form_input.html' with field=name_form.name %}
</div>
<div class="RV-Fieldset">
<button type="submit">{% translate 'Save' %}</button>
</div>
</form>
{% endblock content %}

View File

@@ -0,0 +1,31 @@
from django.urls import path
from .views import (
ImageUploadView,
PublicImageListView,
MyImagesListView,
GalleryCreateView,
MyGalleriesListView,
GalleryDetailView,
ImageDetailView,
GainAccessToGalleryView,
VisitorSettingsView,
RevokeAccessToGallery,
UploadToGalleryView,
ImageThroughGalleryView
)
urlpatterns = [
path('', PublicImageListView.as_view(), name='home'),
path('upload/', ImageUploadView.as_view(), name='upload_image'),
path('my_images/', MyImagesListView.as_view(), name='my_images'),
path('create_gallery/', GalleryCreateView.as_view(), name='create_gallery'),
path('my_galleries/', MyGalleriesListView.as_view(), name='my_galleries'),
path('gallery/<int:pk>', GalleryDetailView.as_view(), name='gallery'),
path('image/<int:pk>', ImageDetailView.as_view(), name='image'),
path('gallery/gain_access/<int:gallery_id>/', GainAccessToGalleryView.as_view(), name='gallery_gain_access'),
path('visitor/my_profile', VisitorSettingsView.as_view(), name='visitor_profile'),
path('gallery/revoke_access/<int:gallery_id>/<int:visitor_id>', RevokeAccessToGallery.as_view(), name='gallery_revoke_access'),
path('gallery/upload/<int:gallery_id>', UploadToGalleryView.as_view(), name='gallery_upload'),
path('gallery/image/<int:gallery_id>/<int:image_id>', ImageThroughGalleryView.as_view(), name='image_through_gallery')
]

View File

@@ -1,3 +1,449 @@
from django.shortcuts import render import secrets
# Create your views here. from django.views import View
from django.views.generic.list import ListView
from django.views.generic.detail import DetailView
from django.contrib.auth.mixins import UserPassesTestMixin
from django.urls import reverse
from django.shortcuts import (
render,
redirect,
get_object_or_404
)
from .forms import (
ImageUploadForm,
GalleryCreatingForm,
AccessCodeForm,
VisitorNameForm,
ImageUploadForm
)
from .models import (
Image,
Visitor,
Gallery
)
class VisitorSessionMixin(View):
def get_visitor(self):
if self.request.session:
if 'visitor_session' in self.request.session:
if Visitor.objects.filter(session_id=self.request.session['visitor_session']).exists():
return Visitor.objects.get(session_id=self.request.session['visitor_session'])
else:
visitor = Visitor.objects.create()
self.request.session['visitor_session'] = visitor.session_id
return visitor
else:
return None
def can_access_image(self, image):
if image.private:
return self.get_visitor() == image.uploaded_by or self.get_visitor() in image.visible_to.all()
else:
return True
def can_access_gallery(self, gallery):
if gallery.private:
return self.get_visitor() == gallery.created_by or self.get_visitor() in gallery.visitors.all()
else:
return True
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['visitor'] = self.get_visitor()
return context
class ImageUploadView(VisitorSessionMixin, View):
def get(self, request):
form = ImageUploadForm()
return render(
request,
'upload_image/upload.html',
{
'form': form,
'visitor': self.get_visitor()
}
)
def post(self, request):
form = ImageUploadForm(
request.POST,
request.FILES
)
if form.is_valid():
image = form.save(commit=False)
if image.private:
image.access_code = secrets.token_hex(16)
image.uploaded_by = self.get_visitor()
image.save()
return redirect(
reverse('home')
)
else:
form = ImageUploadForm()
return render(
request,
'upload_image/upload.html',
{
'form': form,
'visitor': self.get_visitor()
}
)
class PublicImageListView(VisitorSessionMixin, ListView):
model = Image
paginate_by = 20
context_object_name = 'images'
template_name = 'list_images/image_list.html'
def get_queryset(self):
return Image.objects.all().filter(private=False).order_by('-uploaded_when')[:10]
class MyImagesListView(VisitorSessionMixin, View):
def get(self, request):
visitor = self.get_visitor()
uploaded_images = Image.objects.all().filter(
uploaded_by=self.get_visitor()
)
shared_images = Image.objects.all().filter(
visible_to__in=(visitor, )
)
return render(
request,
'image/my_images.html',
{
'uploaded_images': uploaded_images,
'shared_images': shared_images,
'visitor': self.get_visitor()
}
)
class GalleryCreateView(VisitorSessionMixin, View):
def get(self, request):
form = GalleryCreatingForm()
return render(
request,
'gallery/create_gallery.html',
{
'form': form,
'visitor': self.get_visitor()
}
)
def post(self, request):
form = GalleryCreatingForm(
request.POST,
request.FILES
)
if form.is_valid():
gallery = form.save(commit=False)
if gallery.private:
gallery.access_code = secrets.token_hex(16)
gallery.created_by = self.get_visitor()
gallery.save()
for image in request.FILES.getlist('images'):
imageObject = Image.objects.create(
image_file=image,
uploaded_by=gallery.created_by,
private=gallery.private,
access_code=gallery.access_code
)
imageObject.galleries.add(gallery)
imageObject.save()
gallery.save()
return redirect(
reverse(
'gallery',
kwargs={
'pk': gallery.id
}
)
)
else:
form = ImageUploadForm()
return render(
request,
'upload_image/upload.html',
{
'form': form,
'visitor': self.get_visitor()
}
)
class MyGalleriesListView(VisitorSessionMixin, View):
def get(self, request):
visitor = self.get_visitor()
created_galleries = Gallery.objects.all().filter(
created_by=self.get_visitor()
)
invited_galleries = Gallery.objects.all().filter(
visitors__in=(visitor, )
)
return render(
request,
'gallery/my_galleries.html',
{
'created_galleries': created_galleries,
'invited_galleries': invited_galleries,
'visitor': self.get_visitor()
}
)
class GalleryDetailView(VisitorSessionMixin, UserPassesTestMixin, DetailView):
model = Gallery
template_name = 'gallery/gallery_detail.html'
context_object_name = 'gallery'
def test_func(self):
return self.can_access_gallery(
self.get_object()
)
def handle_no_permission(self):
return redirect(
reverse(
'gallery_gain_access',
kwargs={
'gallery_id': self.get_object().id
}
)
)
class ImageDetailView(VisitorSessionMixin, UserPassesTestMixin, DetailView):
model = Image
template_name = 'image/image_detail.html'
context_object_name = 'image'
def test_func(self):
return self.can_access_image(
self.get_object()
)
def handle_no_permission(self):
return redirect(
reverse('home')
)
class ImageThroughGalleryView(VisitorSessionMixin, View):
def _redirect_gallery_no_access(self, gallery):
return redirect(
reverse(
'gallery_gain_access',
kwargs={
'gallery_id': gallery.id
}
)
)
def get(self, request, gallery_id, image_id):
gallery = get_object_or_404(Gallery, id=gallery_id)
if self.get_visitor() in gallery.visitors.all() or self.get_visitor() == gallery.created_by:
image = get_object_or_404(Image, id=image_id)
if image in gallery.images.all():
return render(
request,
'image/image_detail.html',
{
'image': image,
'visitor': self.get_visitor()
}
)
else:
return redirect(
reverse(
'gallery',
kwargs={
'pk': gallery.id
}
)
)
else:
return self._redirect_gallery_no_access(gallery)
class GainAccessToGalleryView(VisitorSessionMixin, View):
def _redirect_to_gallery(self, gallery):
return redirect(
reverse(
'gallery',
kwargs={
'pk': gallery.id
}
)
)
def get(self, request, gallery_id):
gallery = get_object_or_404(
Gallery,
id=gallery_id
)
if self.can_access_gallery(gallery):
return self._redirect_to_gallery(gallery)
else:
return render(
self.request,
'gallery/gallery_no_access.html',
{
'form': AccessCodeForm(),
'visitor': self.get_visitor()
}
)
def post(self, request, gallery_id):
gallery = get_object_or_404(
Gallery,
id=gallery_id
)
if self.can_access_gallery(gallery):
return self._redirect_to_gallery(gallery)
else:
form = AccessCodeForm(
request.POST
)
if form.is_valid():
access_code = form.cleaned_data['access_code']
if access_code == gallery.access_code:
gallery.visitors.add(self.get_visitor())
gallery.save()
self.get_visitor().save()
return self._redirect_to_gallery(gallery)
return render(
self.request,
'gallery/gallery_no_access.html',
{
'form': AccessCodeForm(),
'visitor': self.get_visitor()
}
)
class RevokeAccessToGallery(VisitorSessionMixin, View):
def get(self, request, gallery_id, visitor_id):
gallery = get_object_or_404(Gallery, id=gallery_id)
visitor = self.get_visitor()
if visitor == gallery.created_by:
visitor_to_remove = get_object_or_404(Visitor, id=visitor_id)
if visitor_to_remove in gallery.visitors.all():
gallery.visitors.remove(visitor_to_remove)
gallery.access_code = secrets.token_hex(16)
gallery.save()
return redirect(
reverse(
'gallery',
kwargs={
'pk': gallery.id
}
)
)
class VisitorSettingsView(VisitorSessionMixin, View):
def get(self, request):
name_form = VisitorNameForm(
initial={
'name': self.get_visitor().name
}
)
return render(
request,
'visitor/visitor_settings.html',
{
'name_form': name_form,
'visitor': self.get_visitor()
}
)
def post(self, request):
name_form = VisitorNameForm(
request.POST
)
if name_form.is_valid():
visitor = self.get_visitor()
visitor.name = name_form.cleaned_data['name']
visitor.save()
name_form = VisitorNameForm(
initial={
'name': self.get_visitor().name
}
)
return render(
request,
'visitor/visitor_settings.html',
{
'name_form': name_form,
'visitor': self.get_visitor()
}
)
class UploadToGalleryView(VisitorSessionMixin, View):
def get(self, request, gallery_id):
upload_form = ImageUploadForm()
gallery = get_object_or_404(Gallery, id=gallery_id)
return render(
request,
'gallery/upload_image.html',
{
'form': upload_form,
'gallery': gallery
}
)
def post(self, request, gallery_id):
upload_form = ImageUploadForm(
request.POST,
request.FILES
)
if upload_form.is_valid():
gallery = get_object_or_404(Gallery, id=gallery_id)
for image in request.FILES.getlist('images'):
imageObject = Image.objects.create(
image_file=image,
uploaded_by=self.get_visitor(),
private=gallery.private,
access_code=gallery.access_code
)
imageObject.galleries.add(gallery)
imageObject.save()
gallery.save()
return redirect(
reverse(
'gallery',
kwargs={
'pk': gallery.id
}
)
)
else:
return render(
request,
'gallery/upload_image.html',
{
'form': upload_form,
'gallery': gallery
}
)