Implement image gallery for establishments

This commit is contained in:
Kumi 2021-05-30 12:30:14 +02:00
parent 4ccd5784c4
commit 553b6fae23
10 changed files with 221 additions and 8 deletions

View file

@ -1,3 +1,5 @@
from django.contrib import admin from urlaubsauktion.admin import joker_admin as admin
# Register your models here. from .models import Image
admin.register(Image)

4
gallery/helpers.py Normal file
View file

@ -0,0 +1,4 @@
from django.conf import settings
def get_upload_path(instance, filename):
return instance.upload_path or settings.GALLERY_UPLOAD_PATH

13
gallery/mixins.py Normal file
View file

@ -0,0 +1,13 @@
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
from .models import Image
class GalleryMixin(models.Model):
image_set = GenericRelation(Image)
def add_image(self, image, upload_path=None):
Image.objects.create(image=image, content_object=self, upload_path=upload_path)
class Meta:
abstract = True

View file

@ -1,3 +1,20 @@
from django.db import models from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
# Create your models here. from .helpers import get_upload_path
class Image(models.Model):
image = models.ImageField(upload_to=get_upload_path)
title = models.CharField(max_length=128, null=True, blank=True)
comment = models.TextField(null=True, blank=True)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
upload_path = models.CharField(max_length=256, null=True, blank=True)
@property
def url(self):
return self.image.url

View file

@ -3,6 +3,7 @@ from django.contrib.gis.db import models
from .features import * from .features import *
from localauth.models import User, Profile, LocationMixin, ImageMixin, PhoneMixin from localauth.models import User, Profile, LocationMixin, ImageMixin, PhoneMixin
from gallery.mixins import GalleryMixin
from django_countries.fields import CountryField from django_countries.fields import CountryField
@ -11,7 +12,7 @@ class PartnerProfile(Profile):
def roomcategory_set(self): def roomcategory_set(self):
return RoomCategory.objects.filter(establishment__in=self.establishment_set.all()) return RoomCategory.objects.filter(establishment__in=self.establishment_set.all())
class Establishment(LocationMixin, ImageMixin, PhoneMixin): class Establishment(LocationMixin, ImageMixin, PhoneMixin, GalleryMixin):
owner = models.ForeignKey(PartnerProfile, models.CASCADE) owner = models.ForeignKey(PartnerProfile, models.CASCADE)
name = models.CharField("Name", max_length=64) name = models.CharField("Name", max_length=64)
stars = models.IntegerField("Sterne", null=True, blank=True) stars = models.IntegerField("Sterne", null=True, blank=True)
@ -40,7 +41,7 @@ class Establishment(LocationMixin, ImageMixin, PhoneMixin):
return querysets[0].union(*querysets[1:]) return querysets[0].union(*querysets[1:])
class RoomCategory(ImageMixin): class RoomCategory(GalleryMixin):
establishment = models.ForeignKey(Establishment, models.CASCADE) establishment = models.ForeignKey(Establishment, models.CASCADE)
name = models.CharField("Name", max_length=64) name = models.CharField("Name", max_length=64)
average_price = models.DecimalField("Durchschnittspreis / Nacht", max_digits=10, decimal_places=2) average_price = models.DecimalField("Durchschnittspreis / Nacht", max_digits=10, decimal_places=2)

View file

@ -1,7 +1,7 @@
from django.urls import path, reverse_lazy from django.urls import path, reverse_lazy
from django.views.generic import RedirectView from django.views.generic import RedirectView
from .views import PartnerRegistrationView, PartnerProfileView, OffersListView, EstablishmentsListView, EstablishmentRequestView, PartnerDashboardView, EstablishmentVerificationView, RoomCategoryListView from .views import PartnerRegistrationView, PartnerProfileView, OffersListView, EstablishmentsListView, EstablishmentRequestView, PartnerDashboardView, EstablishmentVerificationView, RoomCategoryListView, EstablishmentGalleryManagementView
app_name = "partners" app_name = "partners"
@ -10,6 +10,7 @@ urlpatterns = [
path('profile/', PartnerProfileView.as_view(), name="profile"), path('profile/', PartnerProfileView.as_view(), name="profile"),
path('establishments/', EstablishmentsListView.as_view(), name="establishments"), path('establishments/', EstablishmentsListView.as_view(), name="establishments"),
path('establishments/<int:id>/', RoomCategoryListView.as_view(), name="roomcategories"), path('establishments/<int:id>/', RoomCategoryListView.as_view(), name="roomcategories"),
path('establishments/<int:id>/gallery/', EstablishmentGalleryManagementView.as_view(), name="establishment_gallery"),
path('establishments/validate/', EstablishmentVerificationView.as_view(), name="establishment_verify"), path('establishments/validate/', EstablishmentVerificationView.as_view(), name="establishment_verify"),
path('establishments/register/', EstablishmentRequestView.as_view(), name="establishment_register"), path('establishments/register/', EstablishmentRequestView.as_view(), name="establishment_register"),
path('offers/', OffersListView.as_view(), name="offers"), path('offers/', OffersListView.as_view(), name="offers"),

View file

@ -11,6 +11,7 @@ from .forms import VerificationForm
from auction.models import Inquiry, Offer from auction.models import Inquiry, Offer
from public.mixins import InConstructionMixin from public.mixins import InConstructionMixin
from localauth.mixins import LoginRequiredMixin, SuperUserRequiredMixin from localauth.mixins import LoginRequiredMixin, SuperUserRequiredMixin
from gallery.models import Image
class PartnerRegistrationView(InConstructionMixin, LoginRequiredMixin, CreateView): class PartnerRegistrationView(InConstructionMixin, LoginRequiredMixin, CreateView):
model = PartnerProfile model = PartnerProfile
@ -153,3 +154,48 @@ class EstablishmentVerificationView(SuperUserRequiredMixin, FormView):
messages.success(self.request, "Unterkunft %s bestätigt!" % eobj[0].name) messages.success(self.request, "Unterkunft %s bestätigt!" % eobj[0].name)
return HttpResponseRedirect(reverse_lazy("partners:establishment_verify")) return HttpResponseRedirect(reverse_lazy("partners:establishment_verify"))
class EstablishmentGalleryManagementView(PartnerProfileRequiredMixin, CreateView, ListView):
model = Image
template_name = "partners/establishment_gallery_manage.html"
fields = ["image", "title", "comment"]
def dispatch(self, request, *args, **kwargs):
self.establishment = self.get_establishment()
self.object_list = self.get_queryset()
if not self.establishment:
return redirect("partners:establishment_register")
return super().dispatch(request, *args, **kwargs)
def get_establishment(self):
establishment = self.kwargs.get("id", None)
kwargs = {"owner": self.request.user.partnerprofile}
if establishment:
kwargs["id"] = establishment
return get_object_or_404(Establishment, **kwargs)
else:
return Establishment.objects.filter(**kwargs).first()
def get_queryset(self):
establishment = self.establishment
return establishment.image_set.all()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["establishment"] = self.establishment
return context
def form_valid(self, form):
form.instance.content_object = self.establishment
for filename, file in self.request.FILES.items():
name = self.request.FILES[filename].name
form.instance.upload_path = f"userfiles/{self.request.user.id}/{name}"
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy("partners:establishment_gallery", args=[self.establishment.id])

View file

@ -0,0 +1,7 @@
from django import template
register = template.Library()
@register.simple_tag
def splitter(l, c=3):
return [l[i:i+c] for i in range(0, len(l), c)]

View file

@ -0,0 +1,120 @@
{% extends "partners/base.html" %}
{% load i18n %}
{% load bootstrap4 %}
{% load splitter %}
{% block "dashboardcontent" %}
<div class="col-12 col-md-10 col-lg-10 dashboard-content booking-trips">
<h2 class="dash-content-title">Deine Bilder</h2>
<div class="dash-content-title"><small>für {{ establishment.name }}</small></div>
<button
class="position-relative btn-primary text-center"
type="button"
data-target="#create-image" data-toggle="modal"
>Bild hochladen</button>
<!-- Carousel wrapper -->
<div
id="carouselMultiItemExample"
class="carousel slide carousel-dark text-center"
data-ride="carousel"
>
<!-- Controls -->
<div class="d-flex justify-content-center mb-4">
<button
class="carousel-control-prev position-relative btn-secondary"
type="button"
data-target="#carouselMultiItemExample"
data-slide="prev"
>
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Zurück</span>
</button>
<button
class="carousel-control-next position-relative btn-secondary"
type="button"
data-target="#carouselMultiItemExample"
data-slide="next"
>
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Weiter</span>
</button>
</div>
<!-- Inner -->
<div class="carousel-inner py-4">
{% splitter object_list 3 as chunks %}
{% for chunk in chunks %}
<!-- Single item -->
<div class="carousel-item {% if forloop.first %}active{% endif %}">
<div class="container">
<div class="row">
{% for image in chunk %}
<div class="col-lg-4 {% if forloop.first %}d-none d-lg-block{% endif %}">
<div class="card">
<img src="{{ image.url }}" class="card-img-top" alt="{{ image.title }}" />
<div class="card-body">
<h5 class="card-title">{{ image.title }}</h5>
<p class="card-text">{{ image.comment }}</p>
<a href="#!" class="btn btn-primary">Löschen</a>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</div>
<!-- Inner -->
</div>
<!-- Carousel wrapper -->
</div>
{% endblock %}
{% block "modal" %}
<div id="create-image" class="modal custom-modal fade" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Bild hochladen</h3>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div><!-- end modal-header -->
<div class="modal-body">
<form enctype="multipart/form-data" method="POST">
{% csrf_token %}
{% bootstrap_form form %}
<button class="btn btn-orange btn-block">Hochladen...</button>
</form>
</div><!-- end modal-bpdy -->
</div><!-- end modal-content -->
</div><!-- end modal-dialog -->
</div><!-- end edit-profile -->
<!-- Modal 1 -->
<div
class="modal fade"
id="exampleModal1"
tabindex="-1"
aria-labelledby="exampleModal1Label"
aria-hidden="true"
>
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="ratio ratio-16x9">
<iframe
src="https://www.youtube.com/embed/A3PDXmYoF5U"
title="YouTube video"
allowfullscreen width="100%" height="100%"
></iframe>
</div>
<div class="text-center py-3">
<button type="button" class="btn btn-secondary" data-dismiss="modal">
Schließen
</button>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -134,3 +134,5 @@ STATICFILES_STORAGE = 'storages.backends.s3boto3.S3StaticStorage' if ENABLE_S3_S
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
PHONENUMBER_DEFAULT_REGION = JOKER_COUNTRIES[0] PHONENUMBER_DEFAULT_REGION = JOKER_COUNTRIES[0]
GALLERY_UPLOAD_PATH = "/userfiles/"