Add Sponsor information for events. Save an event's sponsor to it's team so it can be selected for a future event. Fixes #70

This commit is contained in:
Michael Hall 2018-05-20 12:35:52 -04:00
parent c3a1eefc8d
commit c4408d9a16
10 changed files with 290 additions and 7 deletions

View file

@ -10,6 +10,7 @@ from .models.profiles import (
Member, Member,
Category, Category,
Topic, Topic,
Sponsor,
) )
from .models.search import Searchable from .models.search import Searchable
from .models.events import ( from .models.events import (
@ -54,8 +55,12 @@ class OrgAdmin(admin.ModelAdmin):
list_display = ('name', 'site') list_display = ('name', 'site')
admin.site.register(Organization, OrgAdmin) admin.site.register(Organization, OrgAdmin)
class SponsorAdmin(admin.ModelAdmin):
list_display = ('name', 'web_url')
admin.site.register(Sponsor, SponsorAdmin)
class TeamAdmin(admin.ModelAdmin): class TeamAdmin(admin.ModelAdmin):
raw_id_fields = ('country', 'spr', 'city', 'owner_profile', 'admin_profiles', 'contact_profiles') raw_id_fields = ('country', 'spr', 'city', 'owner_profile', 'admin_profiles', 'contact_profiles', 'sponsors')
list_display = ('__str__', 'active', 'member_count', 'event_count', 'owner_profile', 'created_date', 'is_premium', 'premium_expires') list_display = ('__str__', 'active', 'member_count', 'event_count', 'owner_profile', 'created_date', 'is_premium', 'premium_expires')
list_filter = ('active', 'is_premium', 'organization', ('country',admin.RelatedOnlyFieldListFilter)) list_filter = ('active', 'is_premium', 'organization', ('country',admin.RelatedOnlyFieldListFilter))
ordering = ('-created_date',) ordering = ('-created_date',)
@ -78,7 +83,7 @@ class PlaceAdmin(admin.ModelAdmin):
admin.site.register(Place, PlaceAdmin) admin.site.register(Place, PlaceAdmin)
class EventAdmin(admin.ModelAdmin): class EventAdmin(admin.ModelAdmin):
raw_id_fields = ('place', 'created_by') raw_id_fields = ('place', 'created_by', 'sponsors')
list_display = ('__str__', 'attendee_count', 'start_time', 'created_by', 'created_time') list_display = ('__str__', 'attendee_count', 'start_time', 'created_by', 'created_time')
ordering = ('-start_time',) ordering = ('-start_time',)
def attendee_count(self, event): def attendee_count(self, event):

View file

@ -7,7 +7,7 @@ from django.utils import timezone
from django.contrib.auth.models import User from django.contrib.auth.models import User
from .models.locale import Country, SPR, City from .models.locale import Country, SPR, City
from .models.profiles import Team, UserProfile from .models.profiles import Team, UserProfile, Sponsor
from .models.events import ( from .models.events import (
Event, Event,
EventComment, EventComment,
@ -291,6 +291,11 @@ class EventCommentForm(forms.ModelForm):
model = EventComment model = EventComment
fields = ['body'] fields = ['body']
class SponsorForm(forms.ModelForm):
class Meta:
model = Sponsor
fields = ['name', 'web_url', 'logo']
class NewPlaceForm(forms.ModelForm): class NewPlaceForm(forms.ModelForm):
class Meta: class Meta:
model = Place model = Place

View file

@ -0,0 +1,34 @@
# Generated by Django 2.0 on 2018-05-19 02:10
from django.db import migrations, models
import imagekit.models.fields
class Migration(migrations.Migration):
dependencies = [
('events', '0030_add_attendee_actual_status'),
]
operations = [
migrations.CreateModel(
name='Sponsor',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=256, verbose_name='Sponsor Name')),
('description', models.TextField(blank=True, null=True)),
('web_url', models.URLField(blank=True, null=True, verbose_name='Website')),
('logo', imagekit.models.fields.ProcessedImageField(blank=True, help_text='Will be scaled and cropped to max 250x200 px.', upload_to='sponsors', verbose_name='Sponsor Logo')),
],
),
migrations.AddField(
model_name='event',
name='sponsors',
field=models.ManyToManyField(related_name='events', to='events.Sponsor'),
),
migrations.AddField(
model_name='team',
name='sponsors',
field=models.ManyToManyField(related_name='teams', to='events.Sponsor'),
),
]

View file

@ -77,6 +77,8 @@ class Event(models.Model):
attendees = models.ManyToManyField(UserProfile, through='Attendee', related_name="attending", blank=True) attendees = models.ManyToManyField(UserProfile, through='Attendee', related_name="attending", blank=True)
sponsors = models.ManyToManyField('Sponsor', related_name='events')
@property @property
def is_over(self): def is_over(self):
return self.end_time <= timezone.now() return self.end_time <= timezone.now()

View file

@ -6,7 +6,9 @@ from django.utils import timezone
from django.conf import settings from django.conf import settings
from imagekit.models import ProcessedImageField from imagekit.models import ProcessedImageField
from imagekit.processors import ResizeToFill from imagekit.processors import ResizeToFill, ResizeToFit
from rest_framework import serializers
from .locale import * from .locale import *
from .. import location from .. import location
@ -195,6 +197,29 @@ class Organization(models.Model):
def __str__(self): def __str__(self):
return u'%s' % (self.name) return u'%s' % (self.name)
class Sponsor(models.Model):
name = models.CharField(_("Sponsor Name"), max_length=256, null=False, blank=False)
description = models.TextField(blank=True, null=True)
web_url = models.URLField(_("Website"), null=True, blank=True)
logo = ProcessedImageField(verbose_name=_("Logo"), help_text=_("Will be scaled and cropped to max 250x200 px."),
upload_to='sponsors',
processors=[ResizeToFit(250, 200)],
format='PNG',
blank=True)
def __str__(self):
return self.name
class SponsorSerializer(serializers.ModelSerializer):
display = serializers.CharField(source='__str__', read_only=True)
class Meta:
model = Sponsor
fields = (
'id',
'name',
'logo',
'web_url',
)
class Team(models.Model): class Team(models.Model):
name = models.CharField(_("Team Name"), max_length=256, null=False, blank=False) name = models.CharField(_("Team Name"), max_length=256, null=False, blank=False)
organization = models.ForeignKey(Organization, related_name='teams', null=True, blank=True, on_delete=models.CASCADE) organization = models.ForeignKey(Organization, related_name='teams', null=True, blank=True, on_delete=models.CASCADE)
@ -224,6 +249,8 @@ class Team(models.Model):
category = models.ForeignKey('Category', on_delete=models.SET_NULL, blank=False, null=True) category = models.ForeignKey('Category', on_delete=models.SET_NULL, blank=False, null=True)
topics = models.ManyToManyField('Topic', blank=True) topics = models.ManyToManyField('Topic', blank=True)
sponsors = models.ManyToManyField('Sponsor', related_name='teams')
is_premium = models.BooleanField(default=settings.EVENTS_TEAMS_DEFAULT_PREMIUM) is_premium = models.BooleanField(default=settings.EVENTS_TEAMS_DEFAULT_PREMIUM)
premium_by = models.ForeignKey(UserProfile, related_name='premium_teams', null=True, on_delete=models.SET_NULL) premium_by = models.ForeignKey(UserProfile, related_name='premium_teams', null=True, on_delete=models.SET_NULL)
premium_started = models.DateTimeField(blank=True, null=True) premium_started = models.DateTimeField(blank=True, null=True)

View file

@ -9,7 +9,7 @@ from rest_framework.response import Response
from .models.search import Searchable, SearchableSerializer from .models.search import Searchable, SearchableSerializer
from .models.events import Event, EventComment, Place, PlaceSerializer, Attendee from .models.events import Event, EventComment, Place, PlaceSerializer, Attendee
from .models.locale import Country ,CountrySerializer, SPR, SPRSerializer, City, CitySerializer from .models.locale import Country ,CountrySerializer, SPR, SPRSerializer, City, CitySerializer
from .models.profiles import Team, UserProfile, Member from .models.profiles import Team, UserProfile, Member, Sponsor, SponsorSerializer
from .forms import EventCommentForm from .forms import EventCommentForm
import simplejson import simplejson
@ -78,7 +78,7 @@ def city_list(request, *args, **kwargs):
@api_view(['GET']) @api_view(['GET'])
def find_city(request): def find_city(request):
cities = City.objects.all() cities = City.objects.all()
if "city" in request.GET: if "name" in request.GET:
cities = cities.filter(name=request.GET.get("city")) cities = cities.filter(name=request.GET.get("city"))
if "spr" in request.GET: if "spr" in request.GET:
cities = cities.filter(spr__name=request.GET.get("spr")) cities = cities.filter(spr__name=request.GET.get("spr"))
@ -91,6 +91,19 @@ def find_city(request):
except: except:
return Response({}) return Response({})
@api_view(['GET'])
def sponsor_list(request):
if "q" in request.GET:
match = request.GET.get("q", "")
sponsors = Sponsor.objects.filter(name__icontains=match)
else:
sponsors = Sponsor.objects.all()
serializer = SponsorSerializer(sponsors[:50], many=True)
return Response(serializer.data)
def join_team(request, team_id): def join_team(request, team_id):
if request.user.is_anonymous: if request.user.is_anonymous:
messages.add_message(request, messages.WARNING, message=_('You must be logged in to join a team.')) messages.add_message(request, messages.WARNING, message=_('You must be logged in to join a team.'))

View file

@ -0,0 +1,116 @@
{% extends "get_together/base.html" %}
{% load static markup tz %}
{% block add_to_title %} | {{event.name}}{% endblock %}
{% block styles %}
<link href="{% static 'css/bootstrap-album.css' %}" rel="stylesheet"/>
<style>
.card {
height: 300px !important;
width: 250px !important;
vertical-align: middle;
}
.card-banner {
height: 200px !important;
width: 250px !important;
}
.card-banner .card-img-top {
max-height: 200px !important;
max-width: 248px !important;
}
</style>
{% endblock %}
{% block content %}
<div class="fluid-container">
<div class="row">
<div class="col-sm-9">
<h2>Sponsors for <a href="{{event.get_absolute_url}}">{{ event.name }}</a>
</h2>
<hr/>
{% comment %}
<p>
<b>Lookup sponsors: </b><input type="text" id="id_search" name="search">
<hr/>
</p>
{% endcomment %}
<div class="container">
<div class="row">
{% for sponsor in sponsors %}
<div class="col-md-4">
<div class="card mb-1 box-shadow">
<div class="card-banner align-items-center">
<a class="card-link" href="{{sponsor.web_url}}">
<img class="card-img-top" src="{{sponsor.logo.url}}">
</a>
</div>
<div class="card-body">
<div class="card-text">
<strong>{{sponsor.name}}</strong><br/>
<div class="btn-group">
{% if sponsor in event.sponsors.all %}
<a id="sponsor-button-{{sponsor.id}}" class="btn btn-danger" href="javascript:toggle_sponsor({{sponsor.id}});">Remove</a></span>
{% else %}
<a id="sponsor-button-{{sponsor.id}}" class="btn btn-success" href="javascript:toggle_sponsor({{sponsor.id}});">Add</a></span>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<hr/>
</div>
<div class="col-md-3">
<div class="container">
<div class="row">
<div class="col"><h4>New Sponsor</h4><hr/></div>
</div>
<div class="row">
<div class="col">
<form enctype="multipart/form-data" action="{% url 'manage-event-sponsors' event.id %}" method="POST">
{% csrf_token %}
{{sponsor_form.as_p}}
<button class="btn btn-primary" type="submit">Add Sponsor</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javascript %}
<script type="text/javascript">
function toggle_sponsor(sponsor_id) {
var btn = $("#sponsor-button-"+sponsor_id)
var value = ''
if (btn[0].innerText == 'Add') {
value = 'add'
} else if (btn[0].innerText == 'Remove') {
value = 'remove'
}
$.getJSON("{% url 'sponsor-event' event.id %}?sponsor="+sponsor_id+"&action="+value, function(data, status) {
console.log(data)
if (data.status == "OK") {
if (data.action == "Added") {
btn[0].innerText = "Remove"
btn.removeClass('btn-success').addClass('btn-danger')
} else if (data.action == "Removed") {
btn[0].innerText = "Add"
btn.removeClass('btn-danger').addClass('btn-success')
}
}
});
}
</script>
{% endblock %}

View file

@ -135,6 +135,7 @@
</button> </button>
<div class="dropdown-menu" aria-labelledby="editMenuButton"> <div class="dropdown-menu" aria-labelledby="editMenuButton">
<a href="{% url 'edit-event' event.id %}" class="dropdown-item">Event Details</a> <a href="{% url 'edit-event' event.id %}" class="dropdown-item">Event Details</a>
<a href="{% url 'manage-event-sponsors' event.id %}" class="dropdown-item">Manage Sponsors</a>
<a href="{% url 'schedule-event-talks' event.id %}" class="dropdown-item">Manage Talks</a> <a href="{% url 'schedule-event-talks' event.id %}" class="dropdown-item">Manage Talks</a>
<a href="{% url 'manage-attendees' event.id %}" class="dropdown-item">Manage Attendees</a> <a href="{% url 'manage-attendees' event.id %}" class="dropdown-item">Manage Attendees</a>
</div> </div>
@ -244,6 +245,19 @@
<div class="col-md-3"> <div class="col-md-3">
<div class="container"> <div class="container">
{% if sponsor_count > 0 %}
<div class="row">
<div class="col"><h4>Sponsors</h4><hr/></div>
</div>
{% for sponsor in sponsor_list %}
<div class="row mb-3">
<div class="col">
<a href="{{sponsor.web_url}}" target="_blank"><img src="{{sponsor.logo.url}}" alt="{{sponsor.name}} Logo" title="{{sponsor.name}}"></a>
</div>
</div>
{% endfor %}
<div class="row mb-3"></div>
{% endif %}
<div class="row"> <div class="row">
<div class="col"><h4>Attendees ({{attendee_count}})</h4><hr/></div> <div class="col"><h4>Attendees ({{attendee_count}})</h4><hr/></div>
</div> </div>

View file

@ -81,6 +81,8 @@ urlpatterns = [
path('events/<int:event_id>/+attend/', views.attend_event, name='attend-event'), path('events/<int:event_id>/+attend/', views.attend_event, name='attend-event'),
path('events/<int:event_id>/+attended/', views.attended_event, name='attended-event'), path('events/<int:event_id>/+attended/', views.attended_event, name='attended-event'),
path('events/<int:event_id>/+attendees/', views.manage_attendees, name='manage-attendees'), path('events/<int:event_id>/+attendees/', views.manage_attendees, name='manage-attendees'),
path('events/<int:event_id>/+sponsors/', views.manage_event_sponsors, name='manage-event-sponsors'),
path('events/<int:event_id>/+sponsor/', views.sponsor_event, name='sponsor-event'),
path('events/<int:event_id>/+invite/', views.invite_attendees, name='invite-attendees'), path('events/<int:event_id>/+invite/', views.invite_attendees, name='invite-attendees'),
path('events/<int:event_id>/+delete/', views.delete_event, name='delete-event'), path('events/<int:event_id>/+delete/', views.delete_event, name='delete-event'),
path('events/<int:event_id>/+add_place/', views.add_place_to_event, name='add-place'), path('events/<int:event_id>/+add_place/', views.add_place_to_event, name='add-place'),

View file

@ -1,4 +1,5 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.safestring import mark_safe
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -6,6 +7,7 @@ from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect, reverse, get_object_or_404 from django.shortcuts import render, redirect, reverse, get_object_or_404
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.utils import timezone from django.utils import timezone
from django.utils.datastructures import OrderedSet
from django.core.mail import send_mail from django.core.mail import send_mail
from django.template.loader import get_template, render_to_string from django.template.loader import get_template, render_to_string
from django.conf import settings from django.conf import settings
@ -23,7 +25,7 @@ from events.models.events import (
delete_event_searchable, delete_event_searchable,
) )
from events.models.speakers import Speaker, Talk, SpeakerRequest, Presentation from events.models.speakers import Speaker, Talk, SpeakerRequest, Presentation
from events.models.profiles import Team, Organization, UserProfile, Member from events.models.profiles import Team, Organization, UserProfile, Member, Sponsor
from events.forms import ( from events.forms import (
TeamEventForm, TeamEventForm,
NewTeamEventForm, NewTeamEventForm,
@ -37,6 +39,7 @@ from events.forms import (
EventInviteEmailForm, EventInviteEmailForm,
EventInviteMemberForm, EventInviteMemberForm,
EventContactForm, EventContactForm,
SponsorForm,
) )
from events import location from events import location
@ -73,6 +76,8 @@ def show_event(request, event_id, event_slug):
'team': event.team, 'team': event.team,
'event': event, 'event': event,
'comment_form': comment_form, 'comment_form': comment_form,
'sponsor_count': event.sponsors.count(),
'sponsor_list': event.sponsors.all(),
'is_attending': request.user.profile in event.attendees.all(), 'is_attending': request.user.profile in event.attendees.all(),
'attendee_list': Attendee.objects.filter(event=event).order_by('-status'), 'attendee_list': Attendee.objects.filter(event=event).order_by('-status'),
'attendee_count': Attendee.objects.filter(event=event, status=Attendee.YES).count(), 'attendee_count': Attendee.objects.filter(event=event, status=Attendee.YES).count(),
@ -152,6 +157,66 @@ def create_event(request, team_id):
return redirect('home') return redirect('home')
@login_required
def manage_event_sponsors(request, event_id):
event = get_object_or_404(Event, id=event_id)
if not request.user.profile.can_edit_event(event):
messages.add_message(request, messages.WARNING, message=_('You can not manage this event\'s sponsorss.'))
return redirect(event.get_absolute_url())
if not event.team.is_premium:
messages.add_message(request, messages.ERROR, message=mark_safe(_('Upgrade this team to a <a href="/about/premium">Premium</a> account to use this feature.')))
return redirect(event.get_absolute_url())
team_sponsors = list(event.team.sponsors.all())
events_sponsors = list(Sponsor.objects.filter(events__team=event.team))
if request.method == 'POST':
sponsor_form = SponsorForm(request.POST, request.FILES)
if sponsor_form.is_valid():
new_sponsor = sponsor_form.save()
event.sponsors.add(new_sponsor)
event.team.sponsors.add(new_sponsor)
messages.add_message(request, messages.SUCCESS, message=_('Your sponsor has been added to this event.'))
return redirect('manage-event-sponsors', event.id)
else:
sponsor_form = SponsorForm()
context = {
'event': event,
'sponsors': OrderedSet(events_sponsors + team_sponsors),
'sponsor_form': sponsor_form,
'can_edit_event': request.user.profile.can_edit_event(event),
}
return render(request, 'get_together/events/manage_event_sponsors.html', context)
@login_required
def sponsor_event(request, event_id):
event = get_object_or_404(Event, id=event_id)
sponsor = get_object_or_404(Sponsor, id=request.GET.get('sponsor', None))
if request.user.is_anonymous:
return JsonResponse({'status': 'ERROR', 'message': _("You must be logged in manage event sponsors.")})
if not request.user.profile.can_edit_event(event):
return JsonResponse({'status': 'ERROR', 'message': _("You can not manage this event's sponsors.")})
action = 'none'
if request.GET.get('action', None) == 'add':
if sponsor in event.sponsors.all():
return JsonResponse({'status': 'ERROR', 'message': _("Already sponsoring this event.")})
event.sponsors.add(sponsor)
action = 'Added'
if request.GET.get('action', None) == 'remove':
if sponsor not in event.sponsors.all():
return JsonResponse({'status': 'ERROR', 'message': _("Not sponsoring this event.")})
event.sponsors.remove(sponsor)
action = 'Removed'
return JsonResponse({'status': 'OK', 'sponsor_id': sponsor.id, 'action': action})
@login_required @login_required
def manage_attendees(request, event_id): def manage_attendees(request, event_id):
event = get_object_or_404(Event, id=event_id) event = get_object_or_404(Event, id=event_id)