Add member contact form for team admins.
This commit is contained in:
parent
fb36756ddd
commit
359dd9816e
12 changed files with 238 additions and 40 deletions
|
@ -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:
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
|
@ -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 %}
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue