Skip to content

Commit

Permalink
Update proposal url to use hashid
Browse files Browse the repository at this point in the history
closes #370
  • Loading branch information
Saurabh Kumar committed Mar 31, 2016
1 parent 439f284 commit 03e54fb
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 21 deletions.
2 changes: 1 addition & 1 deletion docs/release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## [0.4.0][0.4.0]

-
- Update proposal urls to use hashids. The slug will now update itself when the title of proposal changes.

## [0.3.0][0.3.0]

Expand Down
11 changes: 10 additions & 1 deletion junction/proposals/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.db import models
from django.template.defaultfilters import slugify
from django.utils.encoding import python_2_unicode_compatible
from django_extensions.db.fields import AutoSlugField
from hashids import Hashids
from simple_history.models import HistoricalRecords

# Junction Stuff
Expand Down Expand Up @@ -96,9 +98,16 @@ def is_public(self):
# TODO: Fix with proper enum
return self.status == 2

def get_slug(self):
return slugify(self.title)

def get_hashid(self):
hashids = Hashids(min_length=5)
return hashids.encode(self.id)

def get_absolute_url(self):
return reverse('proposal-detail',
args=[self.conference.slug, self.slug])
args=[self.conference.slug, self.get_slug(), self.get_hashid()])

def get_update_url(self):
return reverse('proposal-update',
Expand Down
1 change: 1 addition & 0 deletions junction/proposals/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
url(r'^create/$', views.create_proposal, name='proposal-create'),
url(r'^to_review/$', views.proposals_to_review, name='proposals-to-review'),
url(r'^(?P<slug>[\w-]+)/$', views.detail_proposal, name='proposal-detail'),
url(r'^(?P<slug>[\w-]+)~(?P<hashid>.*)/$', views.detail_proposal, name='proposal-detail'),
url(r'^(?P<slug>[\w-]+)/delete/$', views.delete_proposal, name='proposal-delete'),
url(r'^(?P<slug>[\w-]+)/update/$', views.update_proposal, name='proposal-update'),
url(r'^(?P<slug>[\w-]+)/upload-content/$', views.proposal_upload_content, name='proposal-upload-content'),
Expand Down
26 changes: 23 additions & 3 deletions junction/proposals/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from django.http.response import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.views.decorators.http import require_http_methods
from hashids import Hashids

# Junction Stuff
from junction.base.constants import ConferenceSettingConstants, ConferenceStatus, ProposalReviewStatus, ProposalStatus
Expand Down Expand Up @@ -133,9 +134,27 @@ def create_proposal(request, conference_slug):


@require_http_methods(['GET'])
def detail_proposal(request, conference_slug, slug):
conference = get_object_or_404(Conference, slug=conference_slug)
proposal = get_object_or_404(Proposal, slug=slug, conference=conference)
def detail_proposal(request, conference_slug, slug, hashid=None):
"""Display a proposal detail page.
"""
# Here try to get a proposal by it's hashid. If the slug didn't match becase
# the title might have change redirect to the correct slug.
# hashid is optional due to backward compatibility. If the hashid is not present
# we try to still get the proposal by old method i.e. using just the slug, but
# redirect to the correct url which has the hashid.
hashids = Hashids(min_length=5)
id = hashids.decode(hashid)
if id:
proposal = get_object_or_404(Proposal, id=id[0])
if slug != proposal.get_slug():
return HttpResponseRedirect(proposal.get_absolute_url())
else:
conference = get_object_or_404(Conference, slug=conference_slug)
proposal = get_object_or_404(Proposal, slug=slug, conference=conference)
return HttpResponseRedirect(proposal.get_absolute_url())

# Here we have obtained the proposal that we want to display.
conference = proposal.conference
read_private_comment = permissions.is_proposal_author_or_proposal_reviewer(
request.user, conference, proposal)
write_private_comment = permissions.is_proposal_author_or_proposal_section_reviewer(
Expand All @@ -147,6 +166,7 @@ def detail_proposal(request, conference_slug, slug):
public_voting_setting = conference.conferencesetting_set.filter(
name=public_voting['name']).first()
vote_value, voting, public_voting_setting_value = 0, False, True

if public_voting_setting:
public_voting_setting_value = public_voting_setting.value
voting = True if conference.start_date > datetime.now().date() else False
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ django-sampledatahelper==0.3
unicode-slugify==0.1.3
XlsxWriter==0.7.3
arrow==0.7.0
hashids==1.1.0

# Django Model Helpers
# -------------------------------------------------
Expand Down
18 changes: 4 additions & 14 deletions tests/factories.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-

# Standard Library
import uuid
import threading
import datetime
import uuid

# Third Party Stuff
import factory
Expand All @@ -18,15 +18,6 @@ class Meta:
model = None
abstract = True

_SEQUENCE = 1
_SEQUENCE_LOCK = threading.Lock()

@classmethod
def _setup_next_sequence(cls):
with cls._SEQUENCE_LOCK:
cls._SEQUENCE += 1
return cls._SEQUENCE


class UserFactory(Factory):
class Meta:
Expand Down Expand Up @@ -116,11 +107,10 @@ class Meta:
strategy = factory.CREATE_STRATEGY

conference = factory.SubFactory("tests.factories.ConferenceFactory")
proposal_section = factory.SubFactory(
"tests.factories.ProposalSectionFactory")
proposal_section = factory.SubFactory("tests.factories.ProposalSectionFactory")
proposal_type = factory.SubFactory('tests.factories.ProposalTypeFactory')
author = factory.SubFactory("tests.factories.UserFactory")
# title
title = factory.LazyAttribute(lambda x: "Propsoal %s" % x)
# slug
# description
# target_audience
Expand Down
19 changes: 17 additions & 2 deletions tests/integrations/test_proposal_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@


# Proposal


def test_list_proposals_pass(client, settings):
conference = f.create_conference()
url = reverse('proposals-list', kwargs={'conference_slug':
Expand Down Expand Up @@ -246,3 +244,20 @@ def test_proposal_filters(settings, login, conferences):

assert response.status_code == 200
assert response.context['is_filtered'] is True


def test_proposal_detail_url_redirects(client):
proposal = f.create_proposal()
old_url = reverse('proposal-detail', kwargs={'conference_slug': proposal.conference.slug,
'slug': proposal.get_slug()})
response = client.get(old_url)
assert response.status_code == 302
assert proposal.get_absolute_url() in response['Location']

# should redirect the wrong slug, having correct hashid
url = reverse('proposal-detail', kwargs={'conference_slug': proposal.conference.slug,
'slug': 'bla-bla-bla',
'hashid': proposal.get_hashid()})
response = client.get(url)
assert response.status_code == 302
assert proposal.get_absolute_url() in response['Location']

0 comments on commit 03e54fb

Please sign in to comment.