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.profiles import UserProfile, Organization, Team, Member, Category, Topic
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(Continent)
@ -60,12 +60,18 @@ class EventAdmin(admin.ModelAdmin):
admin.site.register(Event, EventAdmin)
class EventPhotoAdmin(admin.ModelAdmin):
raw_id_fields = ('event',)
list_display = ('title', 'event', 'view')
def view(self, photo):
return mark_safe('<a href="%s" target="_blank"><img src="%s" height="90px"></a>' % (photo.src.url, photo.thumbnail.url))
view.short_description = 'Photo'
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):
raw_id_fields = ('place', 'city', 'spr', 'country')
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 .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 time import strptime, strftime
@ -201,6 +201,11 @@ class UploadEventPhotoForm(forms.ModelForm):
model = EventPhoto
fields = ['src', 'title', 'caption']
class EventCommentForm(forms.ModelForm):
class Meta:
model = EventComment
fields = ['body']
class NewPlaceForm(forms.ModelForm):
class Meta:
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 rest_framework import serializers
from mptt.models import MPTTModel, TreeForeignKey
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
@ -198,6 +199,26 @@ class EventPhoto(models.Model):
format='JPEG',
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):
name = models.CharField(max_length=150, verbose_name=_('Event Name'))
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 .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.profiles import Team, UserProfile, Member
from .forms import EventCommentForm
import simplejson
@ -126,3 +127,18 @@ def attend_event(request, event_id):
messages.add_message(request, messages.SUCCESS, message=_("We'll see you there!"))
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',
'imagekit',
'imagekit_cropper',
'mptt',
'get_together',
'events',

View file

@ -18,9 +18,9 @@
position: relative;
}
.gt-profile .gt-profile-badges {
position: absolute;
position: relative;
top: 16px;
left: 6px;
left: -42px;
}
.table td, .table th {
border-top: none;
@ -69,10 +69,76 @@
</tr>
{% endif %}
</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="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 class="row">
{% for photo in event.photos.all %}
@ -98,28 +164,6 @@
{% endif %}
</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>
{% 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>/+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>/+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>/<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 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
@ -35,9 +35,11 @@ def events_list_all(request, *args, **kwargs):
def show_event(request, event_id, event_slug):
event = Event.objects.get(id=event_id)
comment_form = EventCommentForm()
context = {
'team': event.team,
'event': event,
'comment_form': comment_form,
'is_attending': request.user.profile in event.attendees.all(),
'attendee_list': Attendee.objects.filter(event=event),
'can_edit_event': request.user.profile.can_edit_event(event),