Add member contact form for team admins.

This commit is contained in:
Michael Hall 2018-05-09 22:28:08 -04:00
parent fb36756ddd
commit 359dd9816e
12 changed files with 238 additions and 40 deletions

View file

@ -183,6 +183,10 @@ class TeamDefinitionForm(forms.ModelForm):
class DeleteTeamForm(forms.Form): class DeleteTeamForm(forms.Form):
confirm = forms.BooleanField(label="Yes, delete team", required=True) confirm = forms.BooleanField(label="Yes, delete team", required=True)
class TeamContactForm(forms.Form):
to = forms.ChoiceField(label=_(""))
body = forms.CharField(label=_(""), widget=forms.widgets.Textarea)
class TeamEventForm(forms.ModelForm): class TeamEventForm(forms.ModelForm):
recurrences = recurrence.forms.RecurrenceField(label="Repeat", required=False) recurrences = recurrence.forms.RecurrenceField(label="Repeat", required=False)
class Meta: class Meta:

View file

@ -48,10 +48,10 @@
<div class="collapse navbar-collapse" id="navbarsExampleDefault"> <div class="collapse navbar-collapse" id="navbarsExampleDefault">
<ul class="navbar-nav mr-auto"> <ul class="navbar-nav mr-auto">
<li class="nav-item{% if request.resolver_match.url_name == "events" or request.resolver_match.url_name == "all-events" %} active{% endif %}"> <li class="nav-item{% if request.resolver_match.url_name == "events" or request.resolver_match.url_name == "all-events" %} active{% endif %}">
<a class="nav-link" href="{% url 'events' %}">Events{% if request.resolver_match.url_name == "events" %} <span class="sr-only">(current)</span>{% endif %}</a> <a class="nav-link" href="{% url 'events' %}"><i class="fa fa-calendar"></i> Events{% if request.resolver_match.url_name == "events" %} <span class="sr-only">(current)</span>{% endif %}</a>
</li> </li>
<li class="nav-item{% if request.resolver_match.url_name == "teams" or request.resolver_match.url_name == "all-teams" %} active{% endif %}"> <li class="nav-item{% if request.resolver_match.url_name == "teams" or request.resolver_match.url_name == "all-teams" %} active{% endif %}">
<a class="nav-link" href="{% url 'teams' %}">Teams{% if request.resolver_match.url_name == "teams" %} <span class="sr-only">(current)</span>{% endif %}</a> <a class="nav-link" href="{% url 'teams' %}"><i class="fa fa-group"></i> Teams{% if request.resolver_match.url_name == "teams" %} <span class="sr-only">(current)</span>{% endif %}</a>
</li> </li>
{% comment %} {% comment %}
<li class="nav-item{% if request.resolver_match.url_name == "places" %} active{% endif %}"> <li class="nav-item{% if request.resolver_match.url_name == "places" %} active{% endif %}">
@ -66,8 +66,8 @@
<img class="rounded-circle mr-1" src="{{request.user.profile.avatar_url}}" height="24px"/>{% if request.user.profile.realname %}{{ request.user.profile.realname }}{% else %}{{ request.user.username }}{% endif %} <img class="rounded-circle mr-1" src="{{request.user.profile.avatar_url}}" height="24px"/>{% if request.user.profile.realname %}{{ request.user.profile.realname }}{% else %}{{ request.user.username }}{% endif %}
</a> </a>
<div class="dropdown-menu" aria-labelledby="navbarUserMenuLink"> <div class="dropdown-menu" aria-labelledby="navbarUserMenuLink">
<a class="dropdown-item" href="{% url 'show-profile' request.user.profile.id %}">Profile</a> <a class="dropdown-item" href="{% url 'show-profile' request.user.profile.id %}"><i class="fa fa-user"></i> Profile</a>
<a class="dropdown-item" href="{% url 'logout' %}">Logout</a> <a class="dropdown-item" href="{% url 'logout' %}"><i class="fa fa-sign-out"></i> Logout</a>
</div> </div>
</li> </li>
{% else %} {% else %}

View file

@ -0,0 +1,13 @@
{% extends "get_together/emails/base.html" %}
{% block content %}
<h3>Message from {{team.name|striptags}}</h3>
<p><strong>Sender</strong>: {{ sender|striptags }}<br></p>
<p>{{body|striptags}}</p>
<br>
<a href="https://{{site.domain}}{% url 'show-team' team.id %}" title="{{ team.name|striptags }} page.">Go to the team page.</a>
</p>
{% endblock %}

View file

@ -0,0 +1,10 @@
{% block content %}
== Message from {{team.name}} ==
Sender: {{ sender }}
{{ body|striptags }}
Team page: https://{{site.domain}}{% url 'show-team' team.id %}
{% endblock %}

View file

@ -102,17 +102,6 @@
<h2>{{ event.name }} <h2>{{ event.name }}
</h2> </h2>
<p class="text-muted">Hosted by <a href="{% url "show-team" team.id %}">{{ team.name }}</a></p> <p class="text-muted">Hosted by <a href="{% url "show-team" team.id %}">{{ team.name }}</a></p>
{% if can_edit_event %}
<div class="btn-group dropdown">
<button class="btn btn-sm btn-secondary dropdown-toggle" type="button" id="editMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Edit
</button>
<div class="dropdown-menu" aria-labelledby="editMenuButton">
<a href="{% url 'edit-event' event.id %}" class="dropdown-item">Event Details</a>
<a href="{% url 'schedule-event-talks' event.id %}" class="dropdown-item">Talks</a>
</div>
</div>
{% endif %}
{% if settings.SOCIAL_AUTH_TWITTER_KEY %} {% if settings.SOCIAL_AUTH_TWITTER_KEY %}
<a href="https://twitter.com/intent/tweet?text=I'm+having+a+get+together!%0D{{event.name|urlencode}}&original_referer={{event.get_full_url|urlencode}}&url={{event.get_full_url|urlencode}}&hashtags=gettogether" data-size="large" class="btn btn-twitter btn-sm"><i class="fa fa-twitter"></i> Tweet</a> <a href="https://twitter.com/intent/tweet?text=I'm+having+a+get+together!%0D{{event.name|urlencode}}&original_referer={{event.get_full_url|urlencode}}&url={{event.get_full_url|urlencode}}&hashtags=gettogether" data-size="large" class="btn btn-twitter btn-sm"><i class="fa fa-twitter"></i> Tweet</a>
{% endif %} {% endif %}
@ -125,6 +114,17 @@
{% if not is_attending %} {% if not is_attending %}
<a href="{% url 'attend-event' event.id %}" class="btn btn-success btn-sm"><i class="fa fa-check-square-o"></i> Attend</a> <a href="{% url 'attend-event' event.id %}" class="btn btn-success btn-sm"><i class="fa fa-check-square-o"></i> Attend</a>
{% endif %} {% endif %}
{% if can_edit_event %}
<div class="btn-group dropdown">
<button class="btn btn-sm btn-secondary dropdown-toggle" type="button" id="editMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Edit
</button>
<div class="dropdown-menu" aria-labelledby="editMenuButton">
<a href="{% url 'edit-event' event.id %}" class="dropdown-item">Event Details</a>
<a href="{% url 'schedule-event-talks' event.id %}" class="dropdown-item">Talks</a>
</div>
</div>
{% endif %}
<hr/> <hr/>
<p>{{ event.summary|markdown }}</p> <p>{{ event.summary|markdown }}</p>

View file

@ -0,0 +1,82 @@
{% extends "get_together/base.html" %}
{% load markup tz %}
{% block add_to_title %} | {{team.name}}{% endblock %}
{% block styles %}
<style>
.gt-profile {
position: relative;
}
.gt-profile .gt-profile-badges {
position: absolute;
top: 16px;
left: 6px;
}
</style>
{% endblock %}
{% block content %}
<div class="fluid-container">
<div class="row">
<div class="col-sm-9">
<h2>Members of <a href="{% url 'show-team' team.id %}">{{ team.name }}</a>
</h2>
<table class="table">
<tr>
<th colspan="2">Name</th>
<th>Email</th>
<th>Role</th>
<th>Joined</th>
</tr>
{% for member in members %}
<tr>
<td><img class="gt-profile-avatar rounded-circle" src="{{member.user.avatar_url}}" width="32px" height="32px"></td>
<td>{{member.user}}</td>
<td>
{% if member.user.user.account.is_email_confirmed %}
<a href="javascript:contact_member({{member.id}});" class="fa fa-envelope" title="Contact"></a>
{% endif %}
</td>
<td>{{member.role_name}}</td>
<td>{{member.joined_date}}</td>
</tr>
{% endfor %}
</table>
<hr/>
</div>
<div class="col-sm-3">
<div class="container">
<h4>Contact</h4><hr/>
<div class="row">
<div class="col">
<form action="{% url 'manage-members' team.id %}" method="POST">
{% csrf_token %}
{{ contact_form.as_p }}
<button type="submit" class="btn btn-primary btn-sm">Send</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javascript %}
<script type="text/javascript">
$(document).ready(function(){
$("#id_to").selectmenu();
});
function contact_member(member_id) {
$("#id_to").val(member_id);
$("#id_to").selectmenu("refresh");
$("#id_body").focus();
}
</script>
{% endblock %}

View file

@ -22,19 +22,19 @@
<div class="row"> <div class="row">
<div class="col-sm-9"> <div class="col-sm-9">
<h2>Welcome to {{ team.name }} <h2>Welcome to {{ team.name }}
</h2>
{% if can_edit_team %} {% if can_edit_team %}
<a href="{% url 'edit-team' team.id %}" class="btn btn-secondary btn-sm">Edit Team</a> <a href="{% url 'edit-team' team.id %}" class="btn btn-secondary btn-sm"><i class="fa fa-pencil"></i> Edit Team</a>
<a href="{% url 'manage-members' team.id %}" class="btn btn-secondary btn-sm"><i class="fa fa-users"></i> Manage Members</a>
{% endif %} {% endif %}
{% if is_member %} {% if is_member %}
{% if not team.owner_profile == request.user.profile %}<a href="{% url 'leave-team' team.id %}" class="btn btn-danger btn-sm">Leave Team</a>{% endif %} {% if not team.owner_profile == request.user.profile %}<a href="{% url 'leave-team' team.id %}" class="btn btn-danger btn-sm">Leave Team</a>{% endif %}
{% else %} {% else %}
<a href="{% url 'join-team' team.id %}" class="btn btn-success btn-sm">Join Team</a> <a href="{% url 'join-team' team.id %}" class="btn btn-success btn-sm">Join Team</a>
{% endif %} {% endif %}
<a href="{% url 'team-event-ical' team.id %}" class="btn btn-success btn-sm">iCal</a> <hr/>
</h2>
<table >
<table class="table">
{% if team.description %} {% if team.description %}
<tr> <tr>
<td colspan="2"><p>{{ team.description|markdown }}</p></td> <td colspan="2"><p>{{ team.description|markdown }}</p></td>
@ -53,7 +53,9 @@
</table> </table>
<hr/> <hr/>
<h4>Upcoming Events</h4> <h4>Upcoming Events
<small><a href="{% url 'team-event-ical' team.id %}" class="fa fa-calendar" title="iCal"></a></small>
</h4>
<div class="container"> <div class="container">
{% for event in upcoming_events %} {% for event in upcoming_events %}
<div class="row"> <div class="row">
@ -65,7 +67,7 @@
{% if can_create_event %} {% if can_create_event %}
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<br/><a href="{% url 'create-event' team.id %}" class="btn btn-primary">Plan a Get Together</a> <br/><a href="{% url 'create-event' team.id %}" class="btn btn-success"><i class="fa fa-calendar-plus-o"></i> Plan a Get Together</a>
</div> </div>
</div> </div>
{% endif %} {% endif %}

View file

@ -69,6 +69,7 @@ urlpatterns = [
path('team/<int:team_id>/+join/', event_views.join_team, name='join-team'), path('team/<int:team_id>/+join/', event_views.join_team, name='join-team'),
path('team/<int:team_id>/+leave/', event_views.leave_team, name='leave-team'), path('team/<int:team_id>/+leave/', event_views.leave_team, name='leave-team'),
path('team/<int:team_id>/+delete/', views.delete_team, name='delete-team'), path('team/<int:team_id>/+delete/', views.delete_team, name='delete-team'),
path('team/<int:team_id>/+members/', views.manage_members, name='manage-members'),
path('team/<int:team_id>/events.ics', feeds.TeamEventsCalendar(), name='team-event-ical'), path('team/<int:team_id>/events.ics', feeds.TeamEventsCalendar(), name='team-event-ical'),
path('+create-team/', views.start_new_team, name='create-team'), path('+create-team/', views.start_new_team, name='create-team'),

View file

@ -136,6 +136,7 @@ def create_event(request, team_id):
else: else:
return redirect('home') return redirect('home')
@login_required
def add_event_photo(request, event_id): def add_event_photo(request, event_id):
event = get_object_or_404(Event, id=event_id) event = get_object_or_404(Event, id=event_id)
if not request.user.profile.can_edit_event(event): if not request.user.profile.can_edit_event(event):
@ -165,6 +166,7 @@ def add_event_photo(request, event_id):
else: else:
return redirect('home') return redirect('home')
@login_required
def add_place_to_event(request, event_id): def add_place_to_event(request, event_id):
event = get_object_or_404(Event, id=event_id) event = get_object_or_404(Event, id=event_id)
if not request.user.profile.can_edit_event(event): if not request.user.profile.can_edit_event(event):
@ -200,6 +202,7 @@ def add_place_to_event(request, event_id):
else: else:
return redirect('home') return redirect('home')
@login_required
def add_place_to_series(request, series_id): def add_place_to_series(request, series_id):
series = get_object_or_404(EventSeries, id=series_id) series = get_object_or_404(EventSeries, id=series_id)
if not request.user.profile.can_edit_series(series): if not request.user.profile.can_edit_series(series):
@ -232,6 +235,7 @@ def add_place_to_series(request, series_id):
else: else:
return redirect('home') return redirect('home')
@login_required
def edit_event(request, event_id): def edit_event(request, event_id):
event = get_object_or_404(Event, id=event_id) event = get_object_or_404(Event, id=event_id)
@ -276,6 +280,7 @@ def edit_event(request, event_id):
else: else:
return redirect('home') return redirect('home')
@login_required
def delete_event(request, event_id): def delete_event(request, event_id):
event = get_object_or_404(Event, id=event_id) event = get_object_or_404(Event, id=event_id)
if not request.user.profile.can_edit_event(event): if not request.user.profile.can_edit_event(event):
@ -308,6 +313,7 @@ def delete_event(request, event_id):
else: else:
return redirect('home') return redirect('home')
@login_required
def edit_series(request, series_id): def edit_series(request, series_id):
series = get_object_or_404(EventSeries, id=series_id) series = get_object_or_404(EventSeries, id=series_id)
@ -339,6 +345,7 @@ def edit_series(request, series_id):
else: else:
return redirect('home') return redirect('home')
@login_required
def delete_series(request, series_id): def delete_series(request, series_id):
series = get_object_or_404(EventSeries, id=series_id) series = get_object_or_404(EventSeries, id=series_id)
if not request.user.profile.can_edit_series(series): if not request.user.profile.can_edit_series(series):
@ -409,6 +416,7 @@ def create_common_event(request, org_slug):
else: else:
return redirect('home') return redirect('home')
@login_required
def create_common_event_team_select(request, event_id): def create_common_event_team_select(request, event_id):
teams = request.user.profile.moderating teams = request.user.profile.moderating
if len(teams) == 1: if len(teams) == 1:

View file

@ -40,7 +40,7 @@ def show_speaker(request, speaker_id):
context = { context = {
'speaker': speaker, 'speaker': speaker,
'talks': Talk.objects.filter(speaker=speaker), 'talks': Talk.objects.filter(speaker=speaker),
'presentations': Presentation.objects.filter(talk__speaker=speaker, status=Presentation.ACCEPTED), 'presentations': Presentation.objects.filter(talk__speaker=speaker, status=Presentation.ACCEPTED).order_by('-event__start_time'),
} }
return render(request, 'get_together/speakers/show_speaker.html', context) return render(request, 'get_together/speakers/show_speaker.html', context)

View file

@ -3,12 +3,17 @@ from django.utils.translation import ugettext_lazy as _
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import logout as logout_user from django.contrib.auth import logout as logout_user
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.sites.models import Site
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from django.http import HttpResponse, JsonResponse from django.http import HttpResponse, JsonResponse
from django.core.mail import send_mail
from django.template.loader import get_template, render_to_string
from django.conf import settings
from events.models.profiles import Organization, Team, UserProfile, Member from events.models.profiles import Organization, Team, UserProfile, Member
from events.models.events import Event, CommonEvent, Place, Attendee from events.models.events import Event, CommonEvent, Place, Attendee
from events.forms import TeamForm, NewTeamForm, DeleteTeamForm from events.forms import TeamForm, NewTeamForm, DeleteTeamForm, TeamContactForm
from events import location from events import location
import datetime import datetime
@ -51,6 +56,7 @@ def show_team(request, team_id, *args, **kwargs):
} }
return render(request, 'get_together/teams/show_team.html', context) return render(request, 'get_together/teams/show_team.html', context)
@login_required
def create_team(request, *args, **kwargs): def create_team(request, *args, **kwargs):
if request.method == 'GET': if request.method == 'GET':
form = NewTeamForm() form = NewTeamForm()
@ -75,6 +81,7 @@ def create_team(request, *args, **kwargs):
else: else:
return redirect('home') return redirect('home')
@login_required
def edit_team(request, team_id): def edit_team(request, team_id):
team = get_object_or_404(Team, id=team_id) team = get_object_or_404(Team, id=team_id)
if not request.user.profile.can_edit_team(team): if not request.user.profile.can_edit_team(team):
@ -105,6 +112,7 @@ def edit_team(request, team_id):
else: else:
return redirect('home') return redirect('home')
@login_required
def delete_team(request, team_id): def delete_team(request, team_id):
team = get_object_or_404(Team, id=team_id) team = get_object_or_404(Team, id=team_id)
if not request.user.profile.can_edit_team(team): if not request.user.profile.can_edit_team(team):
@ -133,6 +141,86 @@ def delete_team(request, team_id):
else: else:
return redirect('home') return redirect('home')
@login_required
def manage_members(request, team_id):
team = get_object_or_404(Team, id=team_id)
if not request.user.profile.can_edit_team(team):
messages.add_message(request, messages.WARNING, message=_('You can not manage this team\'s members.'))
return redirect('show-team', team_id)
members = Member.objects.filter(team=team).order_by('user__realname')
member_choices = [(member.id, member.user) for member in members if member.user.user.account.is_email_confirmed]
default_choices = [('all', 'All Members (%s)' % len(member_choices)), ('admins', 'Only Administrators')]
if request.method == 'POST':
contact_form = TeamContactForm(request.POST)
contact_form.fields['to'].choices = default_choices + member_choices
if contact_form.is_valid():
to = contact_form.cleaned_data['to']
body = contact_form.cleaned_data['body']
if to is not 'admins' and not request.user.profile.can_edit_team(team):
messages.add_message(request, messages.WARNING, message=_('You can not contact this team\'s members.'))
return redirect('show-team', team_id)
if to == 'all':
count = 0
for member in Member.objects.filter(team=team):
if member.user.user.account.is_email_confirmed:
contact_member(member, body, request.user.profile)
count += 1
messages.add_message(request, messages.SUCCESS, message=_('Emailed %s users' % count))
elif to == 'admins':
count = 0
for member in Member.objects.filter(team=team, role=Member.ADMIN):
if member.user.user.account.is_email_confirmed:
contact_member(member, body, request.user.profile)
count += 1
messages.add_message(request, messages.SUCCESS, message=_('Emailed %s users' % count))
else:
try:
member = Member.objects.get(id=to)
contact_member(member, body, request.user.profile)
messages.add_message(request, messages.SUCCESS, message=_('Emailed %s' % member.user))
except Member.DoesNotExist:
messages.add_message(request, messages.ERROR, message=_('Error sending message: Unknown user (%s)'%to))
pass
return redirect('manage-members', team_id)
else:
messages.add_message(request, messages.ERROR, message=_('Error sending message: %s' % contact_form.errors))
else:
contact_form = TeamContactForm()
contact_form.fields['to'].choices = default_choices + member_choices
context = {
'team': team,
'members': members,
'contact_form': contact_form,
'can_edit_team': request.user.profile.can_edit_team(team),
}
return render(request, 'get_together/teams/manage_members.html', context)
def contact_member(member, body, sender):
context = {
'sender': sender,
'team': member.team,
'body': body,
'site': Site.objects.get(id=1),
}
email_subject = '[GetTogether] Message from %s' % member.team
email_body_text = render_to_string('get_together/emails/member_contact.txt', context)
email_body_html = render_to_string('get_together/emails/member_contact.html', context)
email_recipients = [member.user.user.email]
email_from = getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@gettogether.community')
send_mail(
from_email=email_from,
html_message=email_body_html,
message=email_body_text,
recipient_list=email_recipients,
subject=email_subject,
fail_silently=True,
)
def show_org(request, org_slug): def show_org(request, org_slug):
org = get_object_or_404(Organization, slug=org_slug) org = get_object_or_404(Organization, slug=org_slug)
upcoming_events = CommonEvent.objects.filter(organization=org, end_time__gt=datetime.datetime.now()).order_by('start_time') upcoming_events = CommonEvent.objects.filter(organization=org, end_time__gt=datetime.datetime.now()).order_by('start_time')

View file

@ -1,17 +1,5 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.contrib import messages
from django.contrib.auth import logout as logout_user
from django.shortcuts import render, redirect
from django.http import HttpResponse, JsonResponse
from events.models.locale import City
from events.models.events import Event, Place, Attendee
from events.models.profiles import Team, UserProfile, Member
from events.models.search import Searchable
from events.forms import SearchForm
from accounts.decorators import setup_wanted
from django.conf import settings from django.conf import settings
import datetime import datetime
@ -20,16 +8,13 @@ import geocoder
import math import math
import traceback import traceback
from .teams import *
from .events import *
from .places import *
from .user import *
from .new_user import * from .new_user import *
KM_PER_DEGREE_LAT = 110.574 KM_PER_DEGREE_LAT = 110.574
KM_PER_DEGREE_LNG = 111.320 # At the equator KM_PER_DEGREE_LNG = 111.320 # At the equator
DEFAULT_NEAR_DISTANCE = 100 # kilometeres DEFAULT_NEAR_DISTANCE = 100 # kilometeres
def get_geoip(request): def get_geoip(request):
client_ip = get_client_ip(request) client_ip = get_client_ip(request)
if client_ip == '127.0.0.1' or client_ip == 'localhost': if client_ip == '127.0.0.1' or client_ip == 'localhost':
@ -42,6 +27,7 @@ def get_geoip(request):
g = geocoder.ip(client_ip) g = geocoder.ip(client_ip)
return g return g
def get_nearby_teams(request, near_distance=DEFAULT_NEAR_DISTANCE): def get_nearby_teams(request, near_distance=DEFAULT_NEAR_DISTANCE):
g = get_geoip(request) g = get_geoip(request)
if g.latlng is None or g.latlng[0] is None or g.latlng[1] is None: if g.latlng is None or g.latlng[0] is None or g.latlng[1] is None:
@ -59,6 +45,7 @@ def get_nearby_teams(request, near_distance=DEFAULT_NEAR_DISTANCE):
print("Error looking for local teams: ", e) print("Error looking for local teams: ", e)
return Team.objects.none() return Team.objects.none()
def get_client_ip(request): def get_client_ip(request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for: if x_forwarded_for:
@ -66,3 +53,6 @@ def get_client_ip(request):
else: else:
ip = request.META.get('REMOTE_ADDR') ip = request.META.get('REMOTE_ADDR')
return ip return ip