Add a Team slug for human readable URLs. Fixes #73

This commit is contained in:
Michael Hall 2018-06-08 22:46:07 -04:00
parent 488d1f5c1e
commit 7540b93d0a
15 changed files with 84 additions and 27 deletions

View file

@ -0,0 +1,34 @@
# Generated by Django 2.0 on 2018-06-09 01:44
from django.db import migrations, models
from events.utils import slugify
def add_team_slug(apps, schema_editor):
Team = apps.get_model('events', 'Team')
for team in Team.objects.all():
team.slug = slugify(team.name)
team.save()
class Migration(migrations.Migration):
dependencies = [
('events', '0031_add_sponsors'),
]
operations = [
migrations.AddField(
model_name='team',
name='slug',
field=models.CharField(max_length=256, null=True),
),
migrations.RunPython(add_team_slug, reverse_code=migrations.RunPython.noop),
migrations.AlterField(
model_name='team',
name='slug',
field=models.CharField(default='', max_length=256),
preserve_default=False,
),
]

View file

@ -222,6 +222,7 @@ class SponsorSerializer(serializers.ModelSerializer):
class Team(models.Model): class Team(models.Model):
name = models.CharField(_("Team Name"), max_length=256, null=False, blank=False) name = models.CharField(_("Team Name"), max_length=256, null=False, blank=False)
slug = models.CharField(max_length=256, null=False, blank=False, unique=True)
organization = models.ForeignKey(Organization, related_name='teams', null=True, blank=True, on_delete=models.CASCADE) organization = models.ForeignKey(Organization, related_name='teams', null=True, blank=True, on_delete=models.CASCADE)
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
@ -282,6 +283,12 @@ class Team(models.Model):
if self.city is not None: if self.city is not None:
self.spr = self.city.spr self.spr = self.city.spr
self.country = self.spr.country self.country = self.spr.country
new_slug = slugify(self.name)
slug_matches = list(Team.objects.filter(slug=new_slug))
if len(slug_matches) == 0 or (len(slug_matches) == 1 and slug_matches[0].id == self.id):
self.slug = new_slug
else:
self.slug = '%s-%s' % (new_slug, self.id)
super().save(*args, **kwargs) # Call the "real" save() method. super().save(*args, **kwargs) # Call the "real" save() method.

View file

@ -14,7 +14,7 @@
<div class="col-md-4"> <div class="col-md-4">
<div class="card mb-4 box-shadow"> <div class="card mb-4 box-shadow">
<div class="card-banner"> <div class="card-banner">
<a href="{% url 'show-team' team.id %}"> <a href="{% url 'show-team-by-slug' team.slug %}">
{% if team.category %} {% if team.category %}
<img class="card-img-top" src="{{team.category.img_url}}" alt="{{team.name}}"> <img class="card-img-top" src="{{team.category.img_url}}" alt="{{team.name}}">
{% else %} {% else %}

View file

@ -101,7 +101,7 @@
<div class="col-md-9"> <div class="col-md-9">
<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-by-slug" team.slug %}">{{ team.name }}</a></p>
{% 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 %}

View file

@ -39,7 +39,7 @@
<a href="{% url 'edit-series' series.id %}" class="btn btn-secondary btn-sm">Edit Series</a> <a href="{% url 'edit-series' series.id %}" class="btn btn-secondary btn-sm">Edit Series</a>
{% endif %} {% endif %}
</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-by-slug" team.slug %}">{{ team.name }}</a></p>
<hr/> <hr/>
<p>{{ series.summary|markdown }}</p> <p>{{ series.summary|markdown }}</p>

View file

@ -68,7 +68,7 @@
<div class="col-md-4"> <div class="col-md-4">
<div class="card mb-4 box-shadow"> <div class="card mb-4 box-shadow">
<div class="card-banner"> <div class="card-banner">
<a href="{% url 'show-team' team.id %}"> <a href="{% url 'show-team-by-slug' team.slug %}">
{% if team.category %} {% if team.category %}
<img class="card-img-top" src="{{team.category.img_url}}" alt="{{team.name}}"> <img class="card-img-top" src="{{team.category.img_url}}" alt="{{team.name}}">
{% else %} {% else %}
@ -78,11 +78,11 @@
<p class="card-title">{{team.name}}</p> <p class="card-title">{{team.name}}</p>
</div> </div>
<div class="card-body"> <div class="card-body">
<p class="card-text"><strong><a class="card-link" href="{% url 'show-team' team.id %}">{{team.city}}</a></strong></p> <p class="card-text"><strong><a class="card-link" href="{% url 'show-team-by-slug' team.slug %}">{{team.city}}</a></strong></p>
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center">
<small class="text-muted">{{ team.members.count }} members</small> <small class="text-muted">{{ team.members.count }} members</small>
<div class="btn-group"> <div class="btn-group">
<a class="btn btn-primary" href="{% url 'show-team' team.id %}">View</a></span> <a class="btn btn-primary" href="{% url 'show-team-by-slug' team.slug %}">View</a></span>
</div> </div>
</div> </div>
</div> </div>

View file

@ -14,7 +14,7 @@
<div class="col-md-4"> <div class="col-md-4">
<div class="card mb-4 box-shadow"> <div class="card mb-4 box-shadow">
<div class="card-banner"> <div class="card-banner">
<a href="{% url 'show-team' team.id %}"> <a href="{% url 'show-team-by-slug' team.slug %}">
{% if team.category %} {% if team.category %}
<img class="card-img-top" src="{{team.category.img_url}}" alt="{{team.name}}"> <img class="card-img-top" src="{{team.category.img_url}}" alt="{{team.name}}">
{% else %} {% else %}

View file

@ -65,7 +65,7 @@
<div class="row mb-3"> <div class="row mb-3">
<div class="col media gt-profile"> <div class="col media gt-profile">
<div class="media-body"> <div class="media-body">
<h6 class="mt-0 mb-0"><a href="{% url 'show-team' member.id %}" title="{{member.name}}">{{member.name}}</a></h6> <h6 class="mt-0 mb-0"><a href="{% url 'show-team-by-slug' member.slug %}" title="{{member.name}}">{{member.name}}</a></h6>
</div> </div>
</div> </div>
</div> </div>

View file

@ -7,7 +7,7 @@
<div class="fluid-container"> <div class="fluid-container">
<div class="row"> <div class="row">
<div class="col-sm-9"> <div class="col-sm-9">
<h2>Invite people to <a href="{% url 'show-team' team.id %}">{{ team.name }}</a> <h2>Invite people to <a href="{% url 'show-team-by-slug' team.slug %}">{{ team.name }}</a>
</h2> </h2>
<p>Add a list of emails, separated by commas</p> <p>Add a list of emails, separated by commas</p>
<form action="{% url 'invite-members' team.id %}" method="POST"> <form action="{% url 'invite-members' team.id %}" method="POST">

View file

@ -20,19 +20,21 @@
<div class="col-md-4"> <div class="col-md-4">
<div class="card mb-4 box-shadow"> <div class="card mb-4 box-shadow">
<div class="card-banner"> <div class="card-banner">
<a href="{% url 'show-team-by-slug' team.slug %}">
{% if team.category %} {% if team.category %}
<img class="card-img-top" src="{{team.category.img_url}}" alt="{{team.name}}"> <img class="card-img-top" src="{{team.category.img_url}}" alt="{{team.name}}">
{% else %} {% else %}
<img class="card-img-top" src="{% static 'img/team_placeholder.png' %}" alt="{{team.name}}"> <img class="card-img-top" src="{% static 'img/team_placeholder.png' %}" alt="{{team.name}}">
{% endif %} {% endif %}
</a>
<p class="card-title">{{team.name}}</p> <p class="card-title">{{team.name}}</p>
</div> </div>
<div class="card-body"> <div class="card-body">
<p class="card-text"><strong><a class="card-link" href="{% url 'show-team' team.id %}">{{team.city}}</a></strong></p> <p class="card-text"><strong>{{team.city}}</strong></p>
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center">
<small class="text-muted">{{ team.members.count }} members</small> <small class="text-muted">{{ team.members.count }} members</small>
<div class="btn-group"> <div class="btn-group">
<a class="btn btn-primary" href="{% url 'show-team' team.id %}">View</a></span> <a class="btn btn-primary" href="{% url 'show-team-by-slug' team.slug %}">View</a></span>
</div> </div>
</div> </div>
</div> </div>

View file

@ -21,7 +21,7 @@
<div class="fluid-container"> <div class="fluid-container">
<div class="row"> <div class="row">
<div class="col-sm-9"> <div class="col-sm-9">
<h2>Members of <a href="{% url 'show-team' team.id %}">{{ team.name }}</a> <h2>Members of <a href="{% url 'show-team-by-slug' team.slug %}">{{ team.name }}</a>
</h2> </h2>
<p><a href="{% url 'invite-members' team.id %}" class="btn btn-secondary btn-sm"><i class="fa fa-user-plus"></i> Invite Members</a></p> <p><a href="{% url 'invite-members' team.id %}" class="btn btn-secondary btn-sm"><i class="fa fa-user-plus"></i> Invite Members</a></p>

View file

@ -61,7 +61,7 @@
<ul> <ul>
{% for t in teams %} {% for t in teams %}
<li> <li>
<a href="{% url 'show-team' t.id %}" title="Team page for {{t.name}}">{{t.name}}</a> <a href="{% url 'show-team-by-slug' t.slug %}" title="Team page for {{t.name}}">{{t.name}}</a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>

View file

@ -64,7 +64,7 @@ urlpatterns = [
path('events/all/', views.events_list_all, name='all-events'), path('events/all/', views.events_list_all, name='all-events'),
path('teams/', views.teams_list, name='teams'), path('teams/', views.teams_list, name='teams'),
path('teams/all/', views.teams_list_all, name='all-teams'), path('teams/all/', views.teams_list_all, name='all-teams'),
path('team/<int:team_id>/', views.show_team, name='show-team'), path('team/<int:team_id>/', views.show_team_by_id, name='show-team'),
path('team/<int:team_id>/+edit/', views.edit_team, name='edit-team'), path('team/<int:team_id>/+edit/', views.edit_team, name='edit-team'),
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'),
@ -109,6 +109,8 @@ urlpatterns = [
path('about/', include('django.contrib.flatpages.urls')), path('about/', include('django.contrib.flatpages.urls')),
path('oauth/', include('social_django.urls', namespace='social')), path('oauth/', include('social_django.urls', namespace='social')),
path('<str:team_slug>/', views.show_team_by_slug, name='show-team-by-slug'),
] ]
if settings.DEBUG: if settings.DEBUG:
urlpatterns = urlpatterns + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) urlpatterns = urlpatterns + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

View file

@ -114,7 +114,7 @@ def create_event(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_create_event(team): if not request.user.profile.can_create_event(team):
messages.add_message(request, messages.WARNING, message=_('You can not create events for this team.')) messages.add_message(request, messages.WARNING, message=_('You can not create events for this team.'))
return redirect('show-team', team_id=team.pk) return redirect('show-team-by-slug', team_slug=team.slug)
new_event = Event(team=team, created_by=request.user.profile) new_event = Event(team=team, created_by=request.user.profile)
@ -669,10 +669,10 @@ def delete_event(request, event_id):
elif request.method == 'POST': elif request.method == 'POST':
form = DeleteEventForm(request.POST) form = DeleteEventForm(request.POST)
if form.is_valid() and form.cleaned_data['confirm']: if form.is_valid() and form.cleaned_data['confirm']:
team_id = event.team_id team_slug = event.team.slug
delete_event_searchable(event); delete_event_searchable(event)
event.delete() event.delete()
return redirect('show-team', team_id) return redirect('show-team-by-slug', team_slug)
else: else:
context = { context = {
'team': event.team, 'team': event.team,
@ -734,9 +734,9 @@ def delete_series(request, series_id):
elif request.method == 'POST': elif request.method == 'POST':
form = DeleteEventSeriesForm(request.POST) form = DeleteEventSeriesForm(request.POST)
if form.is_valid() and form.cleaned_data['confirm']: if form.is_valid() and form.cleaned_data['confirm']:
team_id = series.team_id team_slug = series.team.slug
series.delete() series.delete()
return redirect('show-team', team_id) return redirect('show-team-by-slug', team_slug)
else: else:
context = { context = {
'team': series.team, 'team': series.team,

View file

@ -15,6 +15,7 @@ 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, TeamContactForm, TeamInviteForm from events.forms import TeamForm, NewTeamForm, DeleteTeamForm, TeamContactForm, TeamInviteForm
from events import location from events import location
from events.utils import slugify
from accounts.models import EmailRecord from accounts.models import EmailRecord
@ -43,8 +44,18 @@ def teams_list_all(request, *args, **kwargs):
} }
return render(request, 'get_together/teams/list_teams.html', context) return render(request, 'get_together/teams/list_teams.html', context)
def show_team(request, team_id, *args, **kwargs):
def show_team_by_slug(request, team_slug):
team = get_object_or_404(Team, slug=team_slug)
return show_team(request, team)
def show_team_by_id(request, team_id):
team = get_object_or_404(Team, id=team_id) team = get_object_or_404(Team, id=team_id)
return redirect('show-team-by-slug', team_slug=team.slug)
def show_team(request, team):
upcoming_events = Event.objects.filter(team=team, end_time__gt=datetime.datetime.now()).order_by('start_time') upcoming_events = Event.objects.filter(team=team, end_time__gt=datetime.datetime.now()).order_by('start_time')
recent_events = Event.objects.filter(team=team, end_time__lte=datetime.datetime.now()).order_by('-start_time')[:5] recent_events = Event.objects.filter(team=team, end_time__lte=datetime.datetime.now()).order_by('-start_time')[:5]
context = { context = {
@ -58,6 +69,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 @login_required
def create_team(request, *args, **kwargs): def create_team(request, *args, **kwargs):
if request.method == 'GET': if request.method == 'GET':
@ -74,7 +86,7 @@ def create_team(request, *args, **kwargs):
new_team.owner_profile = request.user.profile new_team.owner_profile = request.user.profile
new_team.save() new_team.save()
Member.objects.create(team=new_team, user=request.user.profile, role=Member.ADMIN) Member.objects.create(team=new_team, user=request.user.profile, role=Member.ADMIN)
return redirect('show-team', team_id=new_team.pk) return redirect('show-team-by-slug', team_slug=new_team.slug)
else: else:
context = { context = {
'team_form': form, 'team_form': form,
@ -88,7 +100,7 @@ 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):
messages.add_message(request, messages.WARNING, message=_('You can not make changes to this team.')) messages.add_message(request, messages.WARNING, message=_('You can not make changes to this team.'))
return redirect('show-team', team_id=team.pk) return redirect('show-team-by-slug', team_slug=team.slug)
if request.method == 'GET': if request.method == 'GET':
form = TeamForm(instance=team) form = TeamForm(instance=team)
@ -104,7 +116,7 @@ def edit_team(request, team_id):
new_team = form.save() new_team = form.save()
new_team.owner_profile = request.user.profile new_team.owner_profile = request.user.profile
new_team.save() new_team.save()
return redirect('show-team', team_id=new_team.pk) return redirect('show-team-by-slug', team_slug=new_team.slug)
else: else:
context = { context = {
'team': team, 'team': team,
@ -119,7 +131,7 @@ 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):
messages.add_message(request, messages.WARNING, message=_('You can not make changes to this team.')) messages.add_message(request, messages.WARNING, message=_('You can not make changes to this team.'))
return redirect('show-team', team_id) return redirect('show-team-by-slug', team.slug)
if request.method == 'GET': if request.method == 'GET':
form = DeleteTeamForm() form = DeleteTeamForm()
@ -149,7 +161,7 @@ def manage_members(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):
messages.add_message(request, messages.WARNING, message=_('You can not manage this team\'s members.')) messages.add_message(request, messages.WARNING, message=_('You can not manage this team\'s members.'))
return redirect('show-team', team_id) return redirect('show-team-by-slug', team.slug)
members = Member.objects.filter(team=team).order_by('user__realname') 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] member_choices = [(member.id, member.user) for member in members if member.user.user.account.is_email_confirmed]
@ -162,7 +174,7 @@ def manage_members(request, team_id):
body = contact_form.cleaned_data['body'] body = contact_form.cleaned_data['body']
if to is not 'admins' and not request.user.profile.can_edit_team(team): 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.')) messages.add_message(request, messages.WARNING, message=_('You can not contact this team\'s members.'))
return redirect('show-team', team_id) return redirect('show-team-by-slug', team.slug)
if to == 'all': if to == 'all':
count = 0 count = 0
for member in Member.objects.filter(team=team): for member in Member.objects.filter(team=team):