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):
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):
recurrences = recurrence.forms.RecurrenceField(label="Repeat", required=False)
class Meta:

View file

@ -48,10 +48,10 @@
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
<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 %}">
<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 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>
{% comment %}
<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 %}
</a>
<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 'logout' %}">Logout</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' %}"><i class="fa fa-sign-out"></i> Logout</a>
</div>
</li>
{% 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>
<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 %}
<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 %}
@ -125,6 +114,17 @@
{% 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>
{% 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/>
<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="col-sm-9">
<h2>Welcome to {{ team.name }}
</h2>
{% 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 %}
{% 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 %}
{% else %}
<a href="{% url 'join-team' team.id %}" class="btn btn-success btn-sm">Join Team</a>
{% endif %}
<a href="{% url 'team-event-ical' team.id %}" class="btn btn-success btn-sm">iCal</a>
</h2>
<hr/>
<table class="table">
<table >
{% if team.description %}
<tr>
<td colspan="2"><p>{{ team.description|markdown }}</p></td>
@ -53,7 +53,9 @@
</table>
<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">
{% for event in upcoming_events %}
<div class="row">
@ -65,7 +67,7 @@
{% if can_create_event %}
<div class="row">
<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>
{% 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>/+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>/+members/', views.manage_members, name='manage-members'),
path('team/<int:team_id>/events.ics', feeds.TeamEventsCalendar(), name='team-event-ical'),
path('+create-team/', views.start_new_team, name='create-team'),

View file

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

View file

@ -40,7 +40,7 @@ def show_speaker(request, speaker_id):
context = {
'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)

View file

@ -3,12 +3,17 @@ from django.utils.translation import ugettext_lazy as _
from django.contrib import messages
from django.contrib.auth import logout as logout_user
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.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.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
import datetime
@ -51,6 +56,7 @@ def show_team(request, team_id, *args, **kwargs):
}
return render(request, 'get_together/teams/show_team.html', context)
@login_required
def create_team(request, *args, **kwargs):
if request.method == 'GET':
form = NewTeamForm()
@ -75,6 +81,7 @@ def create_team(request, *args, **kwargs):
else:
return redirect('home')
@login_required
def edit_team(request, team_id):
team = get_object_or_404(Team, id=team_id)
if not request.user.profile.can_edit_team(team):
@ -105,6 +112,7 @@ def edit_team(request, team_id):
else:
return redirect('home')
@login_required
def delete_team(request, team_id):
team = get_object_or_404(Team, id=team_id)
if not request.user.profile.can_edit_team(team):
@ -133,6 +141,86 @@ def delete_team(request, team_id):
else:
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):
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')

View file

@ -1,17 +1,5 @@
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
import datetime
@ -20,16 +8,13 @@ import geocoder
import math
import traceback
from .teams import *
from .events import *
from .places import *
from .user import *
from .new_user import *
KM_PER_DEGREE_LAT = 110.574
KM_PER_DEGREE_LNG = 111.320 # At the equator
DEFAULT_NEAR_DISTANCE = 100 # kilometeres
def get_geoip(request):
client_ip = get_client_ip(request)
if client_ip == '127.0.0.1' or client_ip == 'localhost':
@ -42,6 +27,7 @@ def get_geoip(request):
g = geocoder.ip(client_ip)
return g
def get_nearby_teams(request, near_distance=DEFAULT_NEAR_DISTANCE):
g = get_geoip(request)
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)
return Team.objects.none()
def get_client_ip(request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
@ -66,3 +53,6 @@ def get_client_ip(request):
else:
ip = request.META.get('REMOTE_ADDR')
return ip