Merge pull request #260 from bornhack/feature/rideshare

Ridesharing functionality
This commit is contained in:
Víðir Valberg Guðmundsson 2018-08-10 18:47:56 +02:00 committed by GitHub
commit 426a3f56be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 387 additions and 0 deletions

View file

@ -46,6 +46,7 @@ INSTALLED_APPS = [
'bar',
'backoffice',
'events',
'rideshare',
'allauth',
'allauth.account',

View file

@ -182,6 +182,11 @@ urlpatterns = [
include('teams.urls', namespace='teams')
),
path(
'rideshare/',
include('rideshare.urls', namespace='rideshare')
),
path(
'backoffice/',
include('backoffice.urls', namespace='backoffice')

View file

9
src/rideshare/admin.py Normal file
View file

@ -0,0 +1,9 @@
from django.contrib import admin
from .models import Ride
@admin.register(Ride)
class RideModelAdmin(admin.ModelAdmin):
list_display = ('location', 'when', 'seats', 'user')
list_filter = ('camp', 'user')

5
src/rideshare/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class RideshareConfig(AppConfig):
name = 'rideshare'

View file

@ -0,0 +1,36 @@
# Generated by Django 2.0.4 on 2018-08-08 20:18
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
('camps', '0028_auto_20180525_1025'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Ride',
fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('seats', models.PositiveIntegerField()),
('location', models.CharField(max_length=100)),
('when', models.DateTimeField()),
('description', models.TextField()),
('camp', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='camps.Camp')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
]

View file

30
src/rideshare/models.py Normal file
View file

@ -0,0 +1,30 @@
from django.db import models
from django.urls import reverse
from utils.models import UUIDModel, CampRelatedModel
class Ride(UUIDModel, CampRelatedModel):
camp = models.ForeignKey('camps.Camp', on_delete=models.PROTECT)
user = models.ForeignKey('auth.User', on_delete=models.PROTECT)
seats = models.PositiveIntegerField()
location = models.CharField(max_length=100)
when = models.DateTimeField()
description = models.TextField()
def get_absolute_url(self):
return reverse(
'rideshare:detail',
kwargs={
'pk': self.pk,
'camp_slug': self.camp.slug
}
)
def __str__(self):
return "{} seats from {} at {} by {}".format(
self.seats,
self.location,
self.when,
self.user
)

View file

@ -0,0 +1,13 @@
Hello!<br />
<br />
The following message has been submitted to your rideshare on <a href="{{ rideshare_url }}">{{ rideshare_url }}</a>.<br />
<br />
&lt;message&gt;<br />
<br />
{{ message }}<br />
<br />
&lt;/message&gt;<br />
<br />
Best regards,<br />
<br />
The BornHack Teamp

View file

@ -0,0 +1,13 @@
Hello!
The following message has been submitted to your rideshare on {{ rideshare_url }}.
<message>
{{ message }}
</message>
Best regards,
The BornHack Team

View file

@ -0,0 +1,10 @@
{% extends 'base.html' %}
{% block content %}
<form method="post">{% csrf_token %}
<p>Are you sure you want to delete {{ object }}?</p>
<input type="submit" class="btn btn-danger" value="Confirm" />
</form>
{% endblock %}

View file

@ -0,0 +1,60 @@
{% extends 'base.html' %}
{% load commonmark %}
{% load bootstrap3 %}
{% block content %}
<a class="btn btn-primary" href="{% url 'rideshare:list' camp_slug=camp.slug %}">
<i class="fas fa-chevron-left"></i>
Back
</a>
<hr />
<div class="panel panel-default">
<div class="panel-heading">
<h4>
<strong>{{ object.seats }}</strong>
seats free, going from
<strong>{{ object.location }}</strong>
at
<strong>{{ object.when|date:"jS \o\f F \a\t H:i T" }}</strong>
</h4>
</div>
<div class="panel-body">
<strong>Description:</strong>
<p>
{{ object.description|untrustedcommonmark }}
</p>
</div>
{% if user == object.user %}
<div class="panel-footer">
<a class="btn btn-danger pull-right" href="{% url 'rideshare:delete' camp_slug=camp.slug pk=object.pk %}">
<i class="fas fa-trash"></i>
Delete
</a>
<a class="btn btn-primary pull-right" href="{% url 'rideshare:update' camp_slug=camp.slug pk=object.pk %}">
<i class="fas fa-edit"></i>
Edit
</a>
<span class="clearfix"></span>
</div>
{% else %}
<div class="panel-footer">
<form method="POST">
{% csrf_token %}
{% bootstrap_form form %}
<button type="submit" class="btn btn-success pull-right">
<i class="fas fa-envelope"></i>
Send
</button>
<span class="clearfix"></span>
</form>
</div>
{% endif %}
</div>
{% endblock %}

View file

@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% load commonmark %}
{% load bootstrap3 %}
{% block content %}
<form method="POST">
{% csrf_token %}
{% bootstrap_form form %}
<button type="submit" class="btn btn-success">
{% if object.pk %}Update{% else %}Create{% endif %}
</button>
</form>
{% endblock %}

View file

@ -0,0 +1,54 @@
{% extends 'base.html' %}
{% block content %}
<div class="page-header">
<h1>Ridesharing</h1>
</div>
<p>
On this page participants of {{ camp.title }} can communicate about ridesharing to and from the festival.
</p>
<a class="btn btn-success pull-right" href="{% url 'rideshare:create' camp_slug=camp.slug %}">
<i class="fas fa-car"></i>
Create ride
</a>
<span class="clearfix"></span>
<hr />
<table class="table table-condensed table-striped">
<thead>
<th>
When
<th>
Location
<th>
Seats
<th>
<tbody>
{% for ride in ride_list %}
<tr>
<td>
{{ ride.when|date:"c" }}
<td>
{{ ride.location }}
<td>
{{ ride.seats }}
<td>
<a class="btn btn-primary" href="{% url 'rideshare:detail' camp_slug=camp.slug pk=ride.pk %}">
<i class="fas fa-eye"></i> Details
</a>
{% empty %}
<tr>
<td colspan=4>
No rideshares yet!
{% endfor %}
</table>
{% endblock %}

3
src/rideshare/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

43
src/rideshare/urls.py Normal file
View file

@ -0,0 +1,43 @@
from django.urls import path, include
from .views import (
RideList,
RideCreate,
RideDetail,
RideUpdate,
RideDelete,
)
app_name = 'rideshare'
urlpatterns = [
path(
'',
RideList.as_view(),
name='list'
),
path(
'create/',
RideCreate.as_view(),
name='create'
),
path(
'<uuid:pk>/', include([
path(
'',
RideDetail.as_view(),
name='detail'
),
path(
'update/',
RideUpdate.as_view(),
name='update'
),
path(
'delete/',
RideDelete.as_view(),
name='delete'
),
])
)
]

87
src/rideshare/views.py Normal file
View file

@ -0,0 +1,87 @@
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.urls import reverse
from django.views.generic import (
ListView,
DetailView,
CreateView,
UpdateView,
DeleteView,
FormView
)
from django.http import HttpResponseRedirect
from django import forms
from camps.mixins import CampViewMixin
from utils.email import add_outgoing_email
from .models import Ride
class ContactRideForm(forms.Form):
message = forms.CharField(
widget=forms.Textarea(attrs={"placeholder": "Remember to include your contact information!"}),
label="Write a message to this rideshare",
help_text="ATTENTION!: Pressing send will send an email with the above text. It is up to you to include your contact information so the person receiving the email can contact you.",
)
class RideList(LoginRequiredMixin, CampViewMixin, ListView):
model = Ride
class RideDetail(LoginRequiredMixin, CampViewMixin, DetailView):
model = Ride
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = ContactRideForm()
return context
def post(self, request, **kwargs):
form = ContactRideForm(request.POST)
if form.is_valid():
ride = self.get_object()
add_outgoing_email(
text_template='rideshare/emails/contact_mail.txt',
to_recipients=[ride.user.emailaddress_set.get(primary=True).email],
formatdict=dict(
rideshare_url="https://bornhack.dk{}".format(
reverse(
'rideshare:detail',
kwargs={"camp_slug": self.camp.slug, "pk": ride.pk}
)
),
message=form.cleaned_data['message'],
),
subject="BornHack rideshare message!",
)
messages.info(request, "Your message has been sent.")
return HttpResponseRedirect(ride.get_absolute_url())
class RideCreate(LoginRequiredMixin, CampViewMixin, CreateView):
model = Ride
fields = ['location', 'when', 'seats', 'description']
def form_valid(self, form, **kwargs):
ride = form.save(commit=False)
ride.camp = self.camp
ride.user = self.request.user
ride.save()
self.object = ride
return HttpResponseRedirect(self.get_success_url())
class IsRideOwnerMixin(UserPassesTestMixin):
def test_func(self):
return self.get_object().user == self.request.user
class RideUpdate(LoginRequiredMixin, CampViewMixin, IsRideOwnerMixin, UpdateView):
model = Ride
fields = ['location', 'when', 'seats', 'description']
class RideDelete(LoginRequiredMixin, CampViewMixin, IsRideOwnerMixin, DeleteView):
model = Ride

View file

@ -5,6 +5,9 @@
<a class="btn {% menubuttonclass 'villages' %}" href="{% url 'village_list' camp_slug=camp.slug %}">Villages</a>
<a class="btn {% menubuttonclass 'sponsors' %}" href="{% url 'sponsors' camp_slug=camp.slug %}">Sponsors</a>
<a class="btn {% menubuttonclass 'teams' %}" href="{% url 'teams:list' camp_slug=camp.slug %}">Teams</a>
{% if request.user.is_authenticated %}
<a class="btn {% menubuttonclass 'rideshare' %}" href="{% url 'rideshare:list' camp_slug=camp.slug %}">Rideshare</a>
{% endif %}
{% if request.user.is_staff %}
<a class="btn {% menubuttonclass 'backoffice' %}" href="{% url 'backoffice:index' camp_slug=camp.slug %}">Backoffice</a>
{% endif %}