Add event comments, move photos to the sidebar. Fixes #26
This commit is contained in:
parent
2952a1eb0b
commit
c0f0519c05
9 changed files with 173 additions and 30 deletions
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
47
events/migrations/0020_add_event_comments.py
Normal file
47
events/migrations/0020_add_event_comments.py
Normal 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'),
|
||||
),
|
||||
]
|
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ INSTALLED_APPS = [
|
|||
'social_django',
|
||||
'imagekit',
|
||||
'imagekit_cropper',
|
||||
'mptt',
|
||||
|
||||
'get_together',
|
||||
'events',
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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'),
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in a new issue