Skip to content
Snippets Groups Projects
Commit 32b2471c authored by Nadja Geisler's avatar Nadja Geisler :sunny:
Browse files

Merge branch 'improve-online' into 'main'

Improve virtual rooms

Closes #150 and #179

See merge request kif/akplanning!160
parents 9cc32b5e 82d6ede2
No related branches found
No related tags found
No related merge requests found
Showing
with 1845 additions and 279 deletions
......@@ -13,8 +13,8 @@ from django.utils.translation import gettext_lazy as _
from rest_framework.reverse import reverse
from simple_history.admin import SimpleHistoryAdmin
from AKModel.availability.forms import AvailabilitiesFormMixin
from AKModel.availability.models import Availability
from AKModel.forms import RoomFormWithAvailabilities
from AKModel.models import Event, AKOwner, AKCategory, AKTrack, AKRequirement, AK, AKSlot, Room, AKOrgaMessage, \
ConstraintViolation, DefaultSlot
from AKModel.urls import get_admin_urls_event_wizard, get_admin_urls_event
......@@ -235,30 +235,6 @@ class AKAdmin(SimpleHistoryAdmin):
return HttpResponseRedirect(f"{reverse_lazy('admin:ak-reset-interest-counter')}?pks={','.join(str(pk) for pk in selected)}")
class RoomForm(AvailabilitiesFormMixin, forms.ModelForm):
class Meta:
model = Room
fields = ['name',
'location',
'capacity',
'properties',
'event',
]
widgets = {
'properties': forms.CheckboxSelectMultiple,
}
def __init__(self, *args, **kwargs):
# Init availability mixin
kwargs['initial'] = dict()
super().__init__(*args, **kwargs)
self.initial = {**self.initial, **kwargs['initial']}
# Filter possible values for m2m when event is specified
if hasattr(self.instance, "event") and self.instance.event is not None:
self.fields["properties"].queryset = AKRequirement.objects.filter(event=self.instance.event)
@admin.register(Room)
class RoomAdmin(admin.ModelAdmin):
model = Room
......@@ -268,9 +244,13 @@ class RoomAdmin(admin.ModelAdmin):
ordering = ['location', 'name']
change_form_template = "admin/AKModel/room_change_form.html"
def add_view(self, request, form_url='', extra_context=None):
# Use custom view for room creation (either room form or combined form if virtual rooms are supported)
return redirect("admin:room-new")
def get_form(self, request, obj=None, change=False, **kwargs):
if obj is not None:
return RoomForm
return RoomFormWithAvailabilities
return super().get_form(request, obj, change, **kwargs)
def formfield_for_foreignkey(self, db_field, request, **kwargs):
......@@ -280,6 +260,18 @@ class RoomAdmin(admin.ModelAdmin):
db_field, request, **kwargs
)
def get_urls(self):
if apps.is_installed("AKOnline"):
from AKOnline.views import RoomCreationWithVirtualView as RoomCreationView
else:
from .views import RoomCreationView
urls = [
path('new/', self.admin_site.admin_view(RoomCreationView.as_view()), name="room-new"),
]
urls.extend(super().get_urls())
return urls
class AKSlotAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
......
......@@ -6,7 +6,8 @@ from django import forms
from django.forms.utils import ErrorList
from django.utils.translation import gettext_lazy as _
from AKModel.models import Event, AKCategory, AKRequirement
from AKModel.availability.forms import AvailabilitiesFormMixin
from AKModel.models import Event, AKCategory, AKRequirement, Room
class NewEventWizardStartForm(forms.ModelForm):
......@@ -148,3 +149,37 @@ class RoomBatchCreationForm(AdminIntermediateForm):
raise forms.ValidationError(_("CSV must contain a name column"))
return rooms_raw_dict
class RoomForm(forms.ModelForm):
class Meta:
model = Room
fields = ['name',
'location',
'capacity',
'event',
]
class RoomFormWithAvailabilities(AvailabilitiesFormMixin, RoomForm):
class Meta:
model = Room
fields = ['name',
'location',
'capacity',
'properties',
'event',
]
widgets = {
'properties': forms.CheckboxSelectMultiple,
}
def __init__(self, *args, **kwargs):
# Init availability mixin
kwargs['initial'] = dict()
super().__init__(*args, **kwargs)
self.initial = {**self.initial, **kwargs['initial']}
# Filter possible values for m2m when event is specified
if hasattr(self.instance, "event") and self.instance.event is not None:
self.fields["properties"].queryset = AKRequirement.objects.filter(event=self.instance.event)
This diff is collapsed.
{% extends "admin/base_site.html" %}
{% load tags_AKModel %}
{% load i18n %}
{% load django_bootstrap5 %}
{% load fontawesome_6 %}
{% block title %}{% trans "Create room" %}{% endblock %}
{% block content %}
<h2>{% trans "Create room" %}</h2>
<form method="post">{% csrf_token %}
{% block form_details %}
{% bootstrap_form form %}
{% endblock %}
<div class="float-end">
<button type="submit" name="save_action" class="save btn btn-secondary" value="save_add_another">
{% fa6_icon "plus" 'fas' %} {% trans "Save and add another" %}
</button>
<button type="submit" name="save_action" class="save btn btn-secondary" value="save_continue">
{% fa6_icon "pen" 'fas' %} {% trans "Save and continue editing" %}
</button>
<button type="submit" name="save_action" class="save btn btn-primary" value="save">
{% fa6_icon "check" 'fas' %} {% trans "Save" %}
</button>
</div>
<a href="javascript:history.back()" class="btn btn-info">
{% fa6_icon "times" 'fas' %} {% trans "Cancel" %}
</a>
</form>
{% endblock %}
......@@ -175,8 +175,11 @@ class ModelViewTests(BasicViewTests, TestCase):
self.client.force_login(self.admin_user)
for model in self.ADMIN_MODELS:
if model[1] == "event":
continue
view_name_with_prefix, url = self._name_and_url((f'admin:AKModel_{model[1]}_add', {}))
view_name_with_prefix, url = self._name_and_url((f'admin:new_event_wizard_start', {}))
elif model[1] == "room":
view_name_with_prefix, url = self._name_and_url((f'admin:room-new', {}))
else:
view_name_with_prefix, url = self._name_and_url((f'admin:AKModel_{model[1]}_add', {}))
response = self.client.get(url)
self.assertEqual(response.status_code, 200, msg=f"Add form for model {model[1]} ({url}) broken")
......
......@@ -10,6 +10,7 @@ import django.db
from django.apps import apps
from django.contrib import admin, messages
from django.db.models.functions import Now
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse_lazy, reverse
from django.utils.dateparse import parse_datetime
......@@ -21,7 +22,7 @@ from rest_framework import viewsets, permissions, mixins
from AKModel.forms import NewEventWizardStartForm, NewEventWizardSettingsForm, NewEventWizardPrepareImportForm, \
NewEventWizardImportForm, NewEventWizardActivateForm, AdminIntermediateForm, SlideExportForm, \
AdminIntermediateActionForm, DefaultSlotEditorForm, RoomBatchCreationForm
AdminIntermediateActionForm, DefaultSlotEditorForm, RoomBatchCreationForm, RoomForm
from AKModel.models import Event, AK, AKSlot, Room, AKTrack, AKCategory, AKOwner, AKOrgaMessage, AKRequirement, \
ConstraintViolation, DefaultSlot
from AKModel.serializers import AKSerializer, AKSlotSerializer, RoomSerializer, AKTrackSerializer, AKCategorySerializer, \
......@@ -588,6 +589,25 @@ class DefaultSlotEditorView(EventSlugMixin, IntermediateAdminView):
return super().form_valid(form)
class RoomCreationView(AdminViewMixin, CreateView):
form_class = RoomForm
template_name = 'admin/AKModel/room_create.html'
def get_success_url(self):
print(self.request.POST['save_action'])
if self.request.POST['save_action'] == 'save_add_another':
return reverse_lazy('admin:room-new')
elif self.request.POST['save_action'] == 'save_continue':
return reverse_lazy('admin:AKModel_room_change', kwargs={'object_id': self.room.pk})
else:
return reverse_lazy('admin:AKModel_room_changelist')
def form_valid(self, form):
self.room = form.save()
messages.success(self.request, _("Created Room '%(room)s'" % {'room': self.room}))
return HttpResponseRedirect(self.get_success_url())
class RoomBatchCreationView(EventSlugMixin, IntermediateAdminView):
form_class = RoomBatchCreationForm
title = _("Import Rooms from CSV")
......@@ -611,17 +631,13 @@ class RoomBatchCreationView(EventSlugMixin, IntermediateAdminView):
capacity = raw_room["capacity"] if "capacity" in rooms_raw_dict.fieldnames else -1
try:
r = Room.objects.create(name=name,
location=location,
capacity=capacity,
event=self.event)
if virtual_rooms_support and raw_room["url"] != "":
VirtualRoom.objects.create(name=name,
location=location,
capacity=capacity,
url=raw_room["url"],
event=self.event)
else:
Room.objects.create(name=name,
location=location,
capacity=capacity,
event=self.event)
VirtualRoom.objects.create(room=r,
url=raw_room["url"])
created_count += 1
except django.db.Error as e:
messages.add_message(self.request, messages.WARNING,
......
from django.contrib import admin
from AKModel.admin import RoomAdmin, RoomForm
from AKOnline.models import VirtualRoom
class VirtualRoomForm(RoomForm):
class Meta(RoomForm.Meta):
model = VirtualRoom
fields = ['name',
'location',
'url',
'capacity',
'properties',
'event',
]
@admin.register(VirtualRoom)
class VirtualRoomAdmin(RoomAdmin):
class VirtualRoomAdmin(admin.ModelAdmin):
model = VirtualRoom
list_display = ['room', 'event', 'url']
list_filter = ['room__event']
def get_form(self, request, obj=None, change=False, **kwargs):
if obj is not None:
return VirtualRoomForm
return super().get_form(request, obj, change, **kwargs)
def get_readonly_fields(self, request, obj=None):
# Don't allow changing the room on existing virtual rooms
# Instead, a link to the room editing form will be displayed automatically
if obj:
return self.readonly_fields + ('room', )
return self.readonly_fields
from betterforms.multiform import MultiModelForm
from django.forms import ModelForm
from AKModel.forms import RoomForm
from AKOnline.models import VirtualRoom
class VirtualRoomForm(ModelForm):
class Meta:
model = VirtualRoom
exclude = ['room']
def __init__(self, *args, **kwargs):
super(VirtualRoomForm, self).__init__(*args, **kwargs)
self.fields['url'].required = False
class RoomWithVirtualForm(MultiModelForm):
form_classes = {
'room': RoomForm,
'virtual': VirtualRoomForm
}
def save(self, commit=True):
objects = super(RoomWithVirtualForm, self).save(commit=False)
if commit:
room = objects['room']
room.save()
virtual = objects['virtual']
if virtual.url != "":
virtual.room = room
virtual.save()
else:
objects['virtual'] = None
return objects
# 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: 2023-03-24 18:01+0100\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"
#: AKOnline/models.py:11
msgid "URL"
msgstr "URL"
#: AKOnline/models.py:11
msgid "URL to the room or server"
msgstr "URL zum Raum oder Server"
#: AKOnline/models.py:12
msgid "Room"
msgstr "Raum"
#: AKOnline/models.py:16
#: AKOnline/templates/admin/AKOnline/room_create_with_virtual.html:10
msgid "Virtual Room"
msgstr "Virtueller Raum"
#: AKOnline/models.py:17
msgid "Virtual Rooms"
msgstr "Virtuelle Räume"
#: AKOnline/templates/admin/AKOnline/room_create_with_virtual.html:11
msgid "Leave empty if that room is not virtual/hybrid."
msgstr "Leer lassen wenn der Raum nicht virtuell/hybrid ist"
#: AKOnline/views.py:16
#, python-format
msgid "Created Room '%(room)s'"
msgstr "Raum '%(room)s' angelegt"
#: AKOnline/views.py:18
#, python-format
msgid "Created related Virtual Room '%(vroom)s'"
msgstr "Verbundenen virtuellen Raum '%(vroom)s' angelegt"
# Generated by Django 4.1.5 on 2023-03-22 12:22
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
replaces = [('AKOnline', '0001_AKOnline'), ('AKOnline', '0002_rework_virtual'), ('AKOnline', '0003_rework_virtual_2'), ('AKOnline', '0004_rework_virtual_3'), ('AKOnline', '0005_rework_virtual_4')]
initial = True
dependencies = [
('AKModel', '0033_AKOnline'),
('AKModel', '0057_upgrades'),
]
operations = [
migrations.CreateModel(
name='VirtualRoom',
fields=[
('room', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='virtual', serialize=False, to='AKModel.room', verbose_name='Room')),
('url', models.URLField(blank=True, help_text='URL to the room or server', verbose_name='URL')),
],
options={
'verbose_name': 'Virtual Room',
'verbose_name_plural': 'Virtual Rooms',
},
),
]
# Generated by Django 4.1.5 on 2023-03-21 23:14
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('AKModel', '0057_upgrades'),
('AKOnline', '0001_AKOnline'),
]
operations = [
migrations.RenameModel(
'VirtualRoom',
'VirtualRoomOld'
),
]
# Generated by Django 4.1.5 on 2023-03-21 23:16
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('AKOnline', '0002_rework_virtual'),
]
operations = [
migrations.CreateModel(
name='VirtualRoom',
fields=[
('room', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True,
related_name='virtual', serialize=False, to='AKModel.room',
verbose_name='Room')),
('url', models.URLField(blank=True, help_text='URL to the room or server', verbose_name='URL')),
],
options={
'verbose_name': 'Virtual Room',
'verbose_name_plural': 'Virtual Rooms',
},
),
]
# Generated by Django 4.1.5 on 2023-03-21 23:21
from django.db import migrations
class Migration(migrations.Migration):
atomic = False
dependencies = [
('AKOnline', '0003_rework_virtual_2'),
]
def copy_rooms(apps, schema_editor):
VirtualRoomOld = apps.get_model('AKOnline', 'VirtualRoomOld')
VirtualRoom = apps.get_model('AKOnline', 'VirtualRoom')
for row in VirtualRoomOld.objects.all():
v = VirtualRoom(room_id=row.pk, url=row.url)
v.save()
operations = [
migrations.RunPython(
copy_rooms,
reverse_code=migrations.RunPython.noop,
elidable=True,
)
]
# Generated by Django 4.1.5 on 2023-03-21 23:41
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('AKOnline', '0004_rework_virtual_3'),
]
operations = [
migrations.DeleteModel(
name='VirtualRoomOld',
),
]
......@@ -4,11 +4,21 @@ from django.utils.translation import gettext_lazy as _
from AKModel.models import Event, Room
class VirtualRoom(Room):
""" A virtual room where an AK can be held.
class VirtualRoom(models.Model):
"""
Add details about a virtual or hybrid version of a room to it
"""
url = models.URLField(verbose_name=_("URL"), help_text=_("URL to the room or server"), blank=True)
room = models.OneToOneField(Room, verbose_name=_("Room"), on_delete=models.CASCADE,
related_name='virtual', primary_key=True)
class Meta:
verbose_name = _('Virtual Room')
verbose_name_plural = _('Virtual Rooms')
@property
def event(self):
return self.room.event
def __str__(self):
return f"{self.room.title}: {self.url}"
{% extends "admin/AKModel/room_create.html" %}
{% load tags_AKModel %}
{% load i18n %}
{% load django_bootstrap5 %}
{% block form_details %}
{% bootstrap_form form.room %}
<h3>{% trans "Virtual Room" %}</h3>
<p>{% trans "Leave empty if that room is not virtual/hybrid." %}</p>
{% bootstrap_form form.virtual %}
{% endblock %}
# Create your views here.
from django.contrib import messages
from django.http import HttpResponseRedirect
from django.utils.translation import gettext_lazy as _
from AKModel.views import AdminViewMixin, RoomCreationView
from AKOnline.forms import RoomWithVirtualForm
class RoomCreationWithVirtualView(RoomCreationView):
form_class = RoomWithVirtualForm
template_name = 'admin/AKOnline/room_create_with_virtual.html'
def form_valid(self, form):
objects = form.save()
self.room = objects['room']
messages.success(self.request, _("Created Room '%(room)s'" % {'room': objects['room']}))
if objects['virtual'] is not None:
messages.success(self.request, _("Created related Virtual Room '%(vroom)s'" % {'vroom': objects['virtual']}))
return HttpResponseRedirect(self.get_success_url())
......@@ -58,8 +58,8 @@
<h1>{% trans "Room" %}: {{ room.name }} {% if room.location != '' %}({{ room.location }}){% endif %}</h1>
{% if "AKOnline"|check_app_installed and room.virtualroom and room.virtualroom.url != '' %}
<a class="btn btn-success" target="_parent" href="{{ room.virtualroom.url }}">
{% if "AKOnline"|check_app_installed and room.virtual and room.virtual.url != '' %}
<a class="btn btn-success" target="_parent" href="{{ room.virtual.url }}">
{% fa6_icon 'external-link-alt' 'fas' %} {% trans "Go to virtual room" %}
</a>
{% endif %}
......
......@@ -144,8 +144,8 @@
{% endblocktrans %}
{% endif %}
{% if "AKOnline"|check_app_installed and featured_slot.room.virtualroom and featured_slot.room.virtualroom.url != '' %}
<a class="btn btn-success" target="_parent" href="{{ featured_slot.room.virtualroom.url }}">
{% if "AKOnline"|check_app_installed and featured_slot.room.virtual and featured_slot.room.virtual.url != '' %}
<a class="btn btn-success" target="_parent" href="{{ featured_slot.room.virtual.url }}">
{% fa6_icon 'external-link-alt' 'fas' %} {% trans "Go to virtual room" %}
</a>
{% endif %}
......@@ -272,8 +272,8 @@
data-bs-toggle="tooltip" title="{% trans 'Delete' %}"
class="btn btn-danger">{% fa6_icon 'times' 'fas' %}</a>
{% else %}
{% if "AKOnline"|check_app_installed and slot.room and slot.room.virtualroom and slot.room.virtualroom.url != '' %}
<a class="btn btn-success" target="_parent" href="{{ slot.room.virtualroom.url }}">
{% if "AKOnline"|check_app_installed and slot.room and slot.room.virtual and slot.room.virtual.url != '' %}
<a class="btn btn-success" target="_parent" href="{{ slot.room.virtual.url }}">
{% fa6_icon 'external-link-alt' 'fas' %} {% trans "Go to virtual room" %}
</a>
{% endif %}
......
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment