Add event comments, move photos to the sidebar. Fixes #26

This commit is contained in:
Michael Hall 2018-03-24 00:00:38 -04:00
parent 2952a1eb0b
commit c0f0519c05
9 changed files with 173 additions and 30 deletions

View file

@ -5,7 +5,7 @@ from django.utils.safestring import mark_safe
from .models.locale import Language, Continent, Country, SPR, City from .models.locale import Language, Continent, Country, SPR, City
from .models.profiles import UserProfile, Organization, Team, Member, Category, Topic from .models.profiles import UserProfile, Organization, Team, Member, Category, Topic
from .models.search import Searchable from .models.search import Searchable
from .models.events import Place, Event, EventPhoto, CommonEvent, Attendee from .models.events import Place, Event, EventComment, EventPhoto, CommonEvent, Attendee
admin.site.register(Language) admin.site.register(Language)
admin.site.register(Continent) admin.site.register(Continent)
@ -60,12 +60,18 @@ class EventAdmin(admin.ModelAdmin):
admin.site.register(Event, EventAdmin) admin.site.register(Event, EventAdmin)
class EventPhotoAdmin(admin.ModelAdmin): class EventPhotoAdmin(admin.ModelAdmin):
raw_id_fields = ('event',)
list_display = ('title', 'event', 'view') list_display = ('title', 'event', 'view')
def view(self, photo): def view(self, photo):
return mark_safe('<a href="%s" target="_blank"><img src="%s" height="90px"></a>' % (photo.src.url, photo.thumbnail.url)) return mark_safe('<a href="%s" target="_blank"><img src="%s" height="90px"></a>' % (photo.src.url, photo.thumbnail.url))
view.short_description = 'Photo' view.short_description = 'Photo'
admin.site.register(EventPhoto, EventPhotoAdmin) admin.site.register(EventPhoto, EventPhotoAdmin)
class EventCommentAdmin(admin.ModelAdmin):
raw_id_fields = ('event', 'author')
list_display = ('event', 'author', 'status', 'created_time')
admin.site.register(EventComment, EventCommentAdmin)
class CommonEventAdmin(admin.ModelAdmin): class CommonEventAdmin(admin.ModelAdmin):
raw_id_fields = ('place', 'city', 'spr', 'country') raw_id_fields = ('place', 'city', 'spr', 'country')
list_display = ('__str__', 'participant_count', 'organization', 'start_time', 'country', 'spr', 'city') list_display = ('__str__', 'participant_count', 'organization', 'start_time', 'country', 'spr', 'city')

View file

@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User from django.contrib.auth.models import User
from .models.profiles import Team, UserProfile from .models.profiles import Team, UserProfile
from .models.events import Event, CommonEvent, Place, EventPhoto from .models.events import Event, EventComment ,CommonEvent, Place, EventPhoto
from datetime import time from datetime import time
from time import strptime, strftime from time import strptime, strftime
@ -201,6 +201,11 @@ class UploadEventPhotoForm(forms.ModelForm):
model = EventPhoto model = EventPhoto
fields = ['src', 'title', 'caption'] fields = ['src', 'title', 'caption']
class EventCommentForm(forms.ModelForm):
class Meta:
model = EventComment
fields = ['body']
class NewPlaceForm(forms.ModelForm): class NewPlaceForm(forms.ModelForm):
class Meta: class Meta:
model = Place model = Place

View file

@ -0,0 +1,47 @@
# Generated by Django 2.0 on 2018-03-24 02:55
import datetime
from django.db import migrations, models
import django.db.models.deletion
import mptt.fields
class Migration(migrations.Migration):
dependencies = [
('events', '0019_add_org_slug'),
]
operations = [
migrations.CreateModel(
name='EventComment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('body', models.TextField()),
('created_time', models.DateTimeField(db_index=True, default=datetime.datetime.now)),
('status', models.SmallIntegerField(choices=[(-1, 'Removed'), (0, 'Pending'), (1, 'Approved')], db_index=True, default=1)),
('lft', models.PositiveIntegerField(db_index=True, editable=False)),
('rght', models.PositiveIntegerField(db_index=True, editable=False)),
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
('level', models.PositiveIntegerField(db_index=True, editable=False)),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='eventcomment',
name='author',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.UserProfile'),
),
migrations.AddField(
model_name='eventcomment',
name='event',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.Event'),
),
migrations.AddField(
model_name='eventcomment',
name='parent',
field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='events.EventComment'),
),
]

View file

@ -5,6 +5,7 @@ from django.utils.translation import ugettext_lazy as _
from django.shortcuts import reverse from django.shortcuts import reverse
from rest_framework import serializers from rest_framework import serializers
from mptt.models import MPTTModel, TreeForeignKey
from imagekit.models import ImageSpecField from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill from imagekit.processors import ResizeToFill
@ -198,6 +199,26 @@ class EventPhoto(models.Model):
format='JPEG', format='JPEG',
options={'quality': 60}) options={'quality': 60})
class EventComment(MPTTModel):
REMOVED=-1
PENDING=0
APPROVED=1
STATUSES = [
(REMOVED, _("Removed")),
(PENDING, _("Pending")),
(APPROVED, _("Approved")),
]
author = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
event = models.ForeignKey(Event, related_name='comments', on_delete=models.CASCADE)
body = models.TextField()
created_time = models.DateTimeField(default=datetime.datetime.now, db_index=True)
status = models.SmallIntegerField(choices=STATUSES, default=APPROVED, db_index=True)
parent = TreeForeignKey('self', null=True, blank=True, related_name='children', db_index=True, on_delete=models.SET_NULL)
def __str__(self):
return '%s at %s' % (self.author, self.created_time)
class CommonEvent(models.Model): class CommonEvent(models.Model):
name = models.CharField(max_length=150, verbose_name=_('Event Name')) name = models.CharField(max_length=150, verbose_name=_('Event Name'))
organization = models.ForeignKey(Organization, null=True, blank=True, on_delete=models.CASCADE) organization = models.ForeignKey(Organization, null=True, blank=True, on_delete=models.CASCADE)

View file

@ -7,9 +7,10 @@ from rest_framework.decorators import api_view, throttle_classes
from rest_framework.response import Response from rest_framework.response import Response
from .models.search import Searchable, SearchableSerializer from .models.search import Searchable, SearchableSerializer
from .models.events import Event, 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
from .forms import EventCommentForm
import simplejson import simplejson
@ -126,3 +127,18 @@ def attend_event(request, event_id):
messages.add_message(request, messages.SUCCESS, message=_("We'll see you there!")) messages.add_message(request, messages.SUCCESS, message=_("We'll see you there!"))
return redirect(event.get_absolute_url()) return redirect(event.get_absolute_url())
def comment_event(request, event_id):
event = Event.objects.get(id=event_id)
if request.user.is_anonymous:
messages.add_message(request, messages.WARNING, message=_("You must be logged in to comment."))
return redirect(event.get_absolute_url())
if request.method == 'POST':
new_comment = EventComment(author=request.user.profile, event=event)
comment_form = EventCommentForm(request.POST, instance=new_comment)
if comment_form.is_valid():
new_comment = comment_form.save()
return redirect(event.get_absolute_url()+'#comment-%s'%new_comment.id)
return redirect(event.get_absolute_url())

View file

@ -46,6 +46,7 @@ INSTALLED_APPS = [
'social_django', 'social_django',
'imagekit', 'imagekit',
'imagekit_cropper', 'imagekit_cropper',
'mptt',
'get_together', 'get_together',
'events', 'events',

View file

@ -18,9 +18,9 @@
position: relative; position: relative;
} }
.gt-profile .gt-profile-badges { .gt-profile .gt-profile-badges {
position: absolute; position: relative;
top: 16px; top: 16px;
left: 6px; left: -42px;
} }
.table td, .table th { .table td, .table th {
border-top: none; border-top: none;
@ -69,10 +69,76 @@
</tr> </tr>
{% endif %} {% endif %}
</table> </table>
<br/>
<div class="container mt-3">
</div>
<div class="container mt-3">
<div class="row">
<div class="col"><hr/><h4>Comments</h4></div>
</div>
<div class="row">
{% load mptt_tags %}
<ul class="col list-group">
{% recursetree event.comments.all %}
<div class="list-group-item flex-container">
<div class="row w-100 ml-0">
<div class="w-100 d-flex justify-content-between">
<div class="media gt-profile">
<img class="mr-1 gt-profile-avatar" src="{{node.author.avatar_url}}" width="32px" height="32px">
<span class="gt-profile-badges">{% for badge in node.author.user.account.badges.all %}<img class="mr-0 gt-profile-badge" src="{{badge.img_url}}" title="{{badge.name}}" width="16px" height="16px">{% endfor %}</span>
<a href="{% url 'show-profile' node.author.id %}" title="{{node.author}}'s profile">{{node.author}}</a>
</div>
<span>{{ node.created_time }}</span>
</div>
</div>
<div class="row">
<div class="col ml-5">
<p style="white-space: pre-wrap;">{{ node.body }}</p>
</div>
</div>
{% if not node.is_leaf_node %}
<ul class="list-group children w-100 mt-1">
{{ children }}
</ul>
{% endif %}
</div>
{% endrecursetree %}
{% if can_edit_event %}
<div class="list-group-item pl-5">
<form action="{% url 'comment-event' event.id %}" method="POST">
{% csrf_token %}
{{ comment_form.body }}
<br/><button type="submit" class="btn btn-primary">Comment</button>
</form>
</div>
{% endif %}
</ul>
</div>
</div>
</div>
<div class="col-md-3">
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col"><h4>Photos</h4><hr/></div> <div class="col"><h4>Attendees ({{attendee_list.count}})</h4><hr/></div>
</div>
{% for attendee in attendee_list %}
<div class="row mb-3">
<div class="col media gt-profile">
<img class="mr-1 gt-profile-avatar" src="{{attendee.user.avatar_url}}" width="32px" height="32px">
<span class="gt-profile-badges">{% for badge in attendee.user.user.account.badges.all %}<img class="mr-0 gt-profile-badge" src="{{badge.img_url}}" title="{{badge.name}}" width="16px" height="16px">{% endfor %}</span>
<div class="media-body">
<h6 class="mt-2 mb-0">
<a href="{% url 'show-profile' attendee.user.id %}" title="{{attendee.user}}'s profile">{{attendee.user}}</a>
<span class="badge badge-success align-top">{{ attendee.status_name }}</span></h6>
{% if attendee.role > 0 %}<small class="text-muted">{{ attendee.role_name }}</small>{% endif %}
</div>
</div>
</div>
{% endfor %}
<div class="row">
<div class="col"><hr/><h4>Photos</h4></div>
</div> </div>
<div class="row"> <div class="row">
{% for photo in event.photos.all %} {% for photo in event.photos.all %}
@ -98,28 +164,6 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="col-md-3">
<div class="container">
<div class="row">
<div class="col"><h4>Attendees ({{attendee_list.count}})</h4><hr/></div>
</div>
{% for attendee in attendee_list %}
<div class="row mb-3">
<div class="col media gt-profile">
<img class="mr-1 gt-profile-avatar" src="{{attendee.user.avatar_url}}" width="32px" height="32px">
<span class="gt-profile-badges">{% for badge in attendee.user.user.account.badges.all %}<img class="mr-0 gt-profile-badge" src="{{badge.img_url}}" title="{{badge.name}}" width="16px" height="16px">{% endfor %}</span>
<div class="media-body">
<h6 class="mt-2 mb-0">
<a href="{% url 'show-profile' attendee.user.id %}" title="{{attendee.user}}'s profile">{{attendee.user}}</a>
<span class="badge badge-success align-top">{{ attendee.status_name }}</span></h6>
{% if attendee.role > 0 %}<small class="text-muted">{{ attendee.role_name }}</small>{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -65,6 +65,7 @@ urlpatterns = [
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'),
path('events/<int:event_id>/+share/', views.share_event, name='share-event'), path('events/<int:event_id>/+share/', views.share_event, name='share-event'),
path('events/<int:event_id>/+comment/', event_views.comment_event, name='comment-event'),
path('events/<int:event_id>/+photo/', views.add_event_photo, name='add-event-photo'), path('events/<int:event_id>/+photo/', views.add_event_photo, name='add-event-photo'),
path('events/<int:event_id>/<str:event_slug>/', views.show_event, name='show-event'), path('events/<int:event_id>/<str:event_slug>/', views.show_event, name='show-event'),

View file

@ -7,7 +7,7 @@ from django.shortcuts import render, redirect, reverse
from django.http import HttpResponse, JsonResponse from django.http import HttpResponse, JsonResponse
from events.models.profiles import Team, Organization, UserProfile, Member from events.models.profiles import Team, Organization, UserProfile, Member
from events.forms import TeamEventForm, NewTeamEventForm, DeleteEventForm, NewPlaceForm, UploadEventPhotoForm, NewCommonEventForm from events.forms import TeamEventForm, NewTeamEventForm, DeleteEventForm, EventCommentForm, NewPlaceForm, UploadEventPhotoForm, NewCommonEventForm
from events.models.events import Event, CommonEvent, EventPhoto, Place, Attendee from events.models.events import Event, CommonEvent, EventPhoto, Place, Attendee
@ -35,9 +35,11 @@ def events_list_all(request, *args, **kwargs):
def show_event(request, event_id, event_slug): def show_event(request, event_id, event_slug):
event = Event.objects.get(id=event_id) event = Event.objects.get(id=event_id)
comment_form = EventCommentForm()
context = { context = {
'team': event.team, 'team': event.team,
'event': event, 'event': event,
'comment_form': comment_form,
'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), 'attendee_list': Attendee.objects.filter(event=event),
'can_edit_event': request.user.profile.can_edit_event(event), 'can_edit_event': request.user.profile.can_edit_event(event),