Repository: pretix/pretix

Issue:
Add logo to event organizers
What's about adding a "logo" attribute to event organizers (similar to events) which is displayed on the front page of each event organizer?
Add logo to event organizers
What's about adding a "logo" attribute to event organizers (similar to events) which is displayed on the front page of each event organizer?


Discussion:
(maybe even something like main color, similar to events)
We have very few organizer-based settings, because that was not much of a priority to me so far, but I'd sure take pull requests :)
Hi guys,

I'm new to the project. Happy to contribute. Looks like I'll be taking a crack @ this one. I believe this should set me going in the right direction. 

Also, forgot to mention that you guys did a great job with the doc. 😄 
Thank you very much! :) If you have any questions just ask! 
Sure thing @raphaelm. From what I understand so far, we need to allow `event organizers` add logos to their organizer pages, and maybe a full-width banner to sit on (something that represents the `organizer's' theme`)? Also, maybe we could do the same for `events` because I noticed events don't have that feature as well. Am I understanding this correctly?
Yes. Let's go for the logo first in a similar way that we currently have event logos. 

Once that works, if that's an area that you are interested in, I'd be happy to explore options to allow more custom styling together with you. 

Currently we have logos (that can in theory be a full-width banner) and custom primary colors. However, it would be nice if even more theme customization was possible, I totally agree. It probably will not be that simple if we want to keep security and accessibility high, but I'm sure there are some things that can be improved.
Sounds good. Getting right to it. 😄 
Looks like I'm missing a `pretix.cfg` file. I presume that's the highly confidential project's config file? I have been testing using the default DB (sqlite3).
The `pretix.cfg` structure is detailed [here](https://docs.pretix.eu/en/latest/admin/config.html), development specific settings are mentioned [here](https://docs.pretix.eu/en/latest/development/setup.html?#working-with-mails). Hope this helps :)
Why do you think you are missing it? :) Most development work *should* be possible without one. Let me know if we can improve on the documentation here!
@raphaelm yeah you're right. It would not matter anyway since there are fallback options along the way in `settings.py`. Thanks @rixx, that would come in handy.

Right now, I'm getting this weird error:

```
django.db.utils.OperationalError: no such table: pretixmultidomain_knowndomain
```

I've tried resyncing my DB, making migrations, and re-migrating. None seems to work. 
Works now. Had to`rm` my local SQLite DB,  re-added it, and ran the migrations again.
Okay, strange :( Glad to hear it works now.
Guys, what would be the best way to persist the `logo_image` value from the organizer's update form using the `OrganizerSetting` model? I noticed that these models(`GlobalSetting`, `OrganizerSetting` and `EventSetting`) are being used site-wide to hold key-value pairs of relevant data related to each entity. Since the `OrganizerUpdate` view class is using an `UpdateView`, would it be wise to create an extra attribute on the `Organizer` model or just use the already existing `OrganizerSetting` model?
The ``OrganizerSetting`` can also hold a file as a value (though it is a bit of a hack), so we can use this, but yes, this will require that ``OrganizerUpdate`` contains *both* a ``ModelForm`` and a ``SettingsForm``. You can just look at ``EventUpdate`` and copy from there -- it does exactly the same thing ;)
"The OrganizerSetting can also hold a file as a value (though it is a bit of a hack)" -> yeah I figured :). Thanks a lot! This'll do.
(maybe even something like main color, similar to events)
We have very few organizer-based settings, because that was not much of a priority to me so far, but I'd sure take pull requests :)
Hi guys,

I'm new to the project. Happy to contribute. Looks like I'll be taking a crack @ this one. I believe this should set me going in the right direction. 

Also, forgot to mention that you guys did a great job with the doc. 😄 
Thank you very much! :) If you have any questions just ask! 
Sure thing @raphaelm. From what I understand so far, we need to allow `event organizers` add logos to their organizer pages, and maybe a full-width banner to sit on (something that represents the `organizer's' theme`)? Also, maybe we could do the same for `events` because I noticed events don't have that feature as well. Am I understanding this correctly?
Yes. Let's go for the logo first in a similar way that we currently have event logos. 

Once that works, if that's an area that you are interested in, I'd be happy to explore options to allow more custom styling together with you. 

Currently we have logos (that can in theory be a full-width banner) and custom primary colors. However, it would be nice if even more theme customization was possible, I totally agree. It probably will not be that simple if we want to keep security and accessibility high, but I'm sure there are some things that can be improved.
Sounds good. Getting right to it. 😄 
Looks like I'm missing a `pretix.cfg` file. I presume that's the highly confidential project's config file? I have been testing using the default DB (sqlite3).
The `pretix.cfg` structure is detailed [here](https://docs.pretix.eu/en/latest/admin/config.html), development specific settings are mentioned [here](https://docs.pretix.eu/en/latest/development/setup.html?#working-with-mails). Hope this helps :)
Why do you think you are missing it? :) Most development work *should* be possible without one. Let me know if we can improve on the documentation here!
@raphaelm yeah you're right. It would not matter anyway since there are fallback options along the way in `settings.py`. Thanks @rixx, that would come in handy.

Right now, I'm getting this weird error:

```
django.db.utils.OperationalError: no such table: pretixmultidomain_knowndomain
```

I've tried resyncing my DB, making migrations, and re-migrating. None seems to work. 
Works now. Had to`rm` my local SQLite DB,  re-added it, and ran the migrations again.
Okay, strange :( Glad to hear it works now.
Guys, what would be the best way to persist the `logo_image` value from the organizer's update form using the `OrganizerSetting` model? I noticed that these models(`GlobalSetting`, `OrganizerSetting` and `EventSetting`) are being used site-wide to hold key-value pairs of relevant data related to each entity. Since the `OrganizerUpdate` view class is using an `UpdateView`, would it be wise to create an extra attribute on the `Organizer` model or just use the already existing `OrganizerSetting` model?
The ``OrganizerSetting`` can also hold a file as a value (though it is a bit of a hack), so we can use this, but yes, this will require that ``OrganizerUpdate`` contains *both* a ``ModelForm`` and a ``SettingsForm``. You can just look at ``EventUpdate`` and copy from there -- it does exactly the same thing ;)
"The OrganizerSetting can also hold a file as a value (though it is a bit of a hack)" -> yeah I figured :). Thanks a lot! This'll do.

Resolving patch:
diff --git a/src/pretix/control/forms/organizer.py b/src/pretix/control/forms/organizer.py
--- a/src/pretix/control/forms/organizer.py
+++ b/src/pretix/control/forms/organizer.py
@@ -2,8 +2,9 @@
 from django.core.exceptions import ValidationError
 from django.utils.translation import ugettext_lazy as _
 
-from pretix.base.forms import I18nModelForm
+from pretix.base.forms import I18nModelForm, SettingsForm
 from pretix.base.models import Organizer, Team
+from pretix.control.forms import ExtFileField
 from pretix.multidomain.models import KnownDomain
 
 
@@ -98,3 +99,14 @@ def clean(self):
                                         'the permission to change teams and permissions.'))
 
         return data
+
+
+class OrganizerSettingsForm(SettingsForm):
+
+    organizer_logo_image = ExtFileField(
+        label=_('Logo image'),
+        ext_whitelist=(".png", ".jpg", ".svg", ".gif", ".jpeg"),
+        required=False,
+        help_text=_('If you provide a logo image, we will by default not show your organization name '
+                    'in the page header. We will show your logo with a maximal height of 120 pixels.')
+    )
diff --git a/src/pretix/control/views/organizer.py b/src/pretix/control/views/organizer.py
--- a/src/pretix/control/views/organizer.py
+++ b/src/pretix/control/views/organizer.py
@@ -1,6 +1,7 @@
 from django import forms
 from django.contrib import messages
 from django.core.exceptions import PermissionDenied
+from django.core.files import File
 from django.core.urlresolvers import reverse
 from django.db import transaction
 from django.db.models import Count
@@ -14,7 +15,7 @@
 from pretix.base.models import Organizer, Team, TeamInvite, User
 from pretix.base.services.mail import SendMailException, mail
 from pretix.control.forms.organizer import (
-    OrganizerForm, OrganizerUpdateForm, TeamForm,
+    OrganizerForm, OrganizerSettingsForm, OrganizerUpdateForm, TeamForm,
 )
 from pretix.control.permissions import OrganizerPermissionRequiredMixin
 from pretix.control.signals import nav_organizer
@@ -82,10 +83,47 @@ class OrganizerUpdate(OrganizerPermissionRequiredMixin, UpdateView):
     permission = 'can_change_organizer_settings'
     context_object_name = 'organizer'
 
-    def get_object(self, queryset=None) -> Organizer:
+    @cached_property
+    def object(self) -> Organizer:
         return self.request.organizer
 
+    def get_object(self, queryset=None) -> Organizer:
+        return self.object
+
+    @cached_property
+    def sform(self):
+        return OrganizerSettingsForm(
+            obj=self.object,
+            prefix='settings',
+            data=self.request.POST if self.request.method == 'POST' else None,
+            files=self.request.FILES if self.request.method == 'POST' else None
+        )
+
+    def get_context_data(self, *args, **kwargs) -> dict:
+        context = super().get_context_data(*args, **kwargs)
+        context['sform'] = self.sform
+        return context
+
+    @transaction.atomic
     def form_valid(self, form):
+        self.sform.save()
+        if self.sform.has_changed():
+            self.request.organizer.log_action(
+                'pretix.organizer.settings',
+                user=self.request.user,
+                data={
+                    k: (self.sform.cleaned_data.get(k).name
+                        if isinstance(self.sform.cleaned_data.get(k), File)
+                        else self.sform.cleaned_data.get(k))
+                    for k in self.sform.changed_data
+                }
+            )
+        if form.has_changed():
+            self.request.organizer.log_action(
+                'pretix.organizer.changed',
+                user=self.request.user,
+                data={k: form.cleaned_data.get(k) for k in form.changed_data}
+            )
         messages.success(self.request, _('Your changes have been saved.'))
         return super().form_valid(form)
 
@@ -100,6 +138,13 @@ def get_success_url(self) -> str:
             'organizer': self.request.organizer.slug,
         })
 
+    def post(self, request, *args, **kwargs):
+        form = self.get_form()
+        if form.is_valid() and self.sform.is_valid():
+            return self.form_valid(form)
+        else:
+            return self.form_invalid(form)
+
 
 class OrganizerCreate(CreateView):
     model = Organizer
diff --git a/src/pretix/presale/context.py b/src/pretix/presale/context.py
--- a/src/pretix/presale/context.py
+++ b/src/pretix/presale/context.py
@@ -54,6 +54,10 @@ def contextprocessor(request):
         ctx['event_logo'] = request.event.settings.get('logo_image', as_type=str, default='')[7:]
         ctx['event'] = request.event
 
+    if hasattr(request, 'organizer'):
+        ctx['organizer_logo'] = request.organizer.settings.get('organizer_logo_image', as_type=str, default='')[7:]
+        ctx['organizer'] = request.organizer
+
     ctx['html_head'] = "".join(_html_head)
     ctx['html_foot'] = "".join(_html_foot)
     ctx['footer'] = _footer


Test patch: