Initial Import.

This commit is contained in:
2013-03-31 23:15:07 +01:00
commit c19f2f5562
66 changed files with 1780 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.idea
*.pyc
*.sqlite3

10
app/manage.py Normal file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "vapemap.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)

1
app/stores/__init__.py Normal file
View File

@@ -0,0 +1 @@
__version__ = '0.1'

46
app/stores/admin.py Normal file
View File

@@ -0,0 +1,46 @@
from django.contrib import admin
from .models import Chain, Store, Address, Brand, ClaimRequest
class ChainAdmin(admin.ModelAdmin):
list_filter = ['active']
list_display = ['name']
prepopulated_fields = {"slug": ("name",)}
search_fields = ['name']
class StoreAdmin(admin.ModelAdmin):
list_filter = ['chain', 'active']
list_display = ['name', 'store_type', 'active']
prepopulated_fields = {"slug": ("name",)}
search_fields = ['name']
class ClaimAdmin(admin.ModelAdmin):
list_filter = ['status']
list_display = ['generic_obj', 'user', 'status', 'note']
actions = ['approve_request']
def approve_request(self, request, queryset):
qs = queryset.filter(status=ClaimRequest.CLAIM_STATUS_PENDING)
for obj in qs:
obj.status = ClaimRequest.CLAIM_STATUS_APPROVED
target = obj.generic_obj
target.editor = obj.user
target.save()
obj.save()
if qs.count() == 1:
message_bit = "1 request was"
else:
message_bit = "%s requests were" % qs.count()
self.message_user(request, "%s successfully approved." % message_bit)
approve_request.short_description = 'Approve selected requests.'
admin.site.register(Chain, ChainAdmin)
admin.site.register(Store, StoreAdmin)
admin.site.register(Address, admin.ModelAdmin)
admin.site.register(Brand, admin.ModelAdmin)
admin.site.register(ClaimRequest, ClaimAdmin)

View File

@@ -0,0 +1,16 @@
from django.contrib.sites.models import Site
from .models import ClaimRequest
def site(request):
return {
'site': Site.objects.get_current()
}
def pending_admin(request):
if request.user.is_superuser:
pending = ClaimRequest.objects.filter(status=ClaimRequest.CLAIM_STATUS_PENDING).count()
return {
'admin_pending_requests': pending
}
return {}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

55
app/stores/forms.py Normal file
View File

@@ -0,0 +1,55 @@
from django import forms
from extra_views import InlineFormSet
from epiceditor.widgets import EpicEditorWidget
from .models import ClaimRequest, Store, Address
class BootstrapModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(BootstrapModelForm, self).__init__(*args, **kwargs)
if hasattr(self.Meta, 'classes'):
for field, css in self.Meta.classes.items():
if field in self.fields:
self.fields[field].widget.attrs['class'] = css
class ClaimRequestForm(BootstrapModelForm):
def __init__(self, *args, **kwargs):
super(ClaimRequestForm, self).__init__(*args, **kwargs)
for field, css in self.Meta.classes.items():
if field in self.fields:
self.fields[field].widget.attrs['class'] = css
class Meta:
model = ClaimRequest
fields = ('note',)
classes = {
'note': 'input-xxlarge',
}
class StoreForm(BootstrapModelForm):
class Meta:
model = Store
exclude = ('slug', 'address', 'chain', 'editor')
classes = {
'name': 'input-xxlarge',
'long_description': 'input-xxlarge',
}
widgets = {
'long_description': EpicEditorWidget(attrs={'rows': 40}, themes={'editor':'epic-light.css'})
}
class AddressForm(BootstrapModelForm):
class Meta:
model = Address
exclude = ('name', 'geo_latitude', 'geo_longitude')
class AddressInline(InlineFormSet):
model = Address

207
app/stores/models.py Normal file
View File

@@ -0,0 +1,207 @@
import re
from django.db import models
from django.core.urlresolvers import reverse_lazy
from django.contrib.auth import get_user_model
from django.contrib.gis.geos import Point
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from .utils import caching_geo_lookup
USER_MODEL = get_user_model()
class Chain(models.Model):
name = models.CharField('Name', max_length=200)
slug = models.SlugField('URL Slug', max_length=200, blank=True)
head_office = models.ForeignKey('stores.Address', blank=True, null=True)
website = models.URLField('Website', blank=True, null=True)
editor = models.ForeignKey(USER_MODEL, related_name='editable_chains', blank=True, null=True)
active = models.BooleanField('Active?', default=True)
long_description = models.TextField('Description', blank=True)
def save(self, **kwargs):
if self.slug == '':
self.slug = re.sub(r'\W+', '-', str(self.name).lower())
return super(Chain, self).save(**kwargs)
def get_absolute_url(self):
return reverse_lazy('chain-detail', args=[self.slug])
def __unicode__(self):
return self.name
class Meta:
ordering = ['name']
class Store(models.Model):
STORE_TYPE_RETAIL = 1
STORE_TYPE_ONLINE = 2
STORE_TYPE_BOTH = 3
STORE_TYPE_CHOICES = (
(STORE_TYPE_RETAIL, 'Retail Only'),
(STORE_TYPE_ONLINE, 'Online Only'),
(STORE_TYPE_BOTH, 'Retail & Online'),
)
name = models.CharField('Name', max_length=200, help_text="Store's full name")
slug = models.SlugField('URL Slug', max_length=200, blank=True)
address = models.ForeignKey('stores.Address', related_name='stores')
store_type = models.IntegerField('Store Type', choices=STORE_TYPE_CHOICES)
chain = models.ForeignKey(Chain, related_name='stores', null=True, blank=True)
editor = models.ForeignKey(USER_MODEL, related_name='editable_stores', null=True, blank=True)
active = models.BooleanField('Active?', default=True)
website = website = models.URLField('Website', null=True, blank=True)
email = models.EmailField('Email', null=True, blank=True, help_text="Contact email address for the store.")
phone = models.CharField('Phone', max_length=25, blank=True, help_text="Contact phone number for the store.")
long_description = models.TextField('Description', blank=True, help_text="Full description of the store, including any marketing material. Markdown supported.")
brands = models.ManyToManyField('stores.Brand', null=True, blank=True, help_text="Brands that are sold by this store.")
def get_full_address(self):
if self.address:
return self.address.full_address
def get_location(self):
if self.address:
return self.address.geo_location
def save(self, **kwargs):
if not self.slug or self.slug == '':
self.slug = re.sub(r'\W+', '-', str(self.name).lower())
return super(Store, self).save(**kwargs)
def get_absolute_url(self):
return reverse_lazy('store-detail', args=[self.slug])
def __unicode__(self):
if not self.name and self.chain:
return self.chain.name
return self.name
class Meta:
ordering = ['name']
class Brand(models.Model):
name = models.CharField('Brand Name', max_length=200)
website = models.URLField('Brand Website', null=True, blank=True)
def __unicode__(self):
return self.name
class Meta:
ordering = ['name']
class County(models.Model):
"""
UK Counties
"""
name = models.CharField('Name', max_length=100)
@property
def stores(self):
return Store.objects.filter(address__county__pk=self.pk)
def __unicode__(self):
return self.name
class Meta:
ordering = ['name']
class Country(models.Model):
"""
ISO Countries
"""
iso_code = models.CharField('ISO Code', max_length=3)
name = models.CharField('Name', max_length=100)
@property
def stores(self):
return Store.objects.filter(address__country__pk=self.pk)
def __unicode__(self):
return self.name
class Meta:
ordering = ['name']
class Address(models.Model):
"""
Represents a location or postal address
"""
name = models.CharField('Address Name', max_length=200)
address1 = models.CharField('Address Line 1', max_length=200, help_text="First line of the address. e.g. <em>1 Station Road</em>")
address2 = models.CharField('Address Line 2', max_length=200, blank=True, help_text="(Optional) Second line of the address")
address3 = models.CharField('Address Line 3', max_length=200, blank=True, help_text="(Optional) Third line of the address")
city = models.CharField('Town/City', max_length='50', help_text="City or town")
county = models.ForeignKey('stores.County', related_name='addresses', null=True, blank=True, help_text="County or suburban area.")
country = models.ForeignKey('stores.Country', related_name='addresses')
postcode = models.CharField('Postcode', max_length=20, help_text="Post Code, e.g. <em>M1 1AA</em>")
geo_latitude = models.FloatField('Latitude', null=True, blank=True)
geo_longitude = models.FloatField('Longitude', null=True, blank=True)
@property
def full_address(self):
return u', '.join([
self.address1,
self.address2,
self.address3,
self.city,
unicode(self.county),
self.postcode,
unicode(self.country),
])
@property
def address_string(self):
return u', '.join([
self.address1,
self.postcode,
unicode(self.country),
])
@property
def geo_location(self):
return Point(self.geo_longitude, self.geo_latitude)
def __unicode__(self):
if self.name:
return self.name
return self.address_string
def save(self, **kwargs):
if not self.geo_latitude and not self.geo_longitude:
res = caching_geo_lookup(self.address_string)
if res:
self.geo_latitude, self.geo_longitude = res[1]
return super(Address, self).save(**kwargs)
class ClaimRequest(models.Model):
CLAIM_STATUS_PENDING = 0
CLAIM_STATUS_APPROVED = 1
CLAIM_STATUS_REJECTED = 2
CLAIM_STATUS_CHOICES = (
(CLAIM_STATUS_PENDING, 'Pending'),
(CLAIM_STATUS_APPROVED, 'Approved'),
(CLAIM_STATUS_REJECTED, 'Rejected')
)
object_id = models.PositiveIntegerField()
object_type = models.ForeignKey(ContentType)
generic_obj = generic.GenericForeignKey('object_type', 'object_id')
user = models.ForeignKey(USER_MODEL, related_name='claims')
note = models.TextField('Claim Note')
status = models.IntegerField('Status', choices=CLAIM_STATUS_CHOICES, default=CLAIM_STATUS_PENDING)

View File

@@ -0,0 +1,15 @@
from haystack import indexes
from .models import Store
class StoreIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
address = indexes.CharField(model_attr='address__full_address')
location = indexes.LocationField(model_attr='address__geo_location')
def get_model(self):
return Store
def index_queryset(self, using=None):
"""Used when the entire index for model is updated."""
return self.get_model().objects.filter(active=True)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1000 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,17 @@
function supports_geolocation() {
return 'geolocation' in navigator;
}
function init_geolocation() {
if (supports_geolocation()) {
navigator.geolocation.getCurrentPosition(handle_geolocation_event)
}
}
function handle_geolocation_event(position) {
$('#geolocation a').attr('href', '/stores/search/?lat=' + position.coords.latitude + '&lng=' + position.coords.longitude + '&distance=10');
$('#geolocation').fadeIn(1000);
}
$(document).ready(function() {init_geolocation();})

View File

@@ -0,0 +1,45 @@
var store_icon = '/static/img/map_icons/home-2.png'
var online_icon = '/static/img/map_icons/wifi.png'
var geolocation_icon = '/static/img/map_icons/home-2-green.png'
function lookup_marker(id) {
if (id == null || id == 1) {
return store_icon
} else if (id == 2) {
return online_icon
} else if (id == 999) {
return geolocation_icon
} else {
return store_icon
}
}
function initialize_map(markers, element) {
var mapOptions = {
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(element, mapOptions);
var bounds = new google.maps.LatLngBounds();
for (var i = 0; i < markers.length; i++) {
marker = markers[i];
if (marker[1] != null && marker[2] != null) {
var latlng = new google.maps.LatLng(marker[1], marker[2]);
bounds.extend(latlng);
var marker_obj = new google.maps.Marker({
position: latlng,
map: map,
title: marker[0],
icon: lookup_marker(marker[3])
});
if (marker[4] != '') {
google.maps.event.addListener(marker_obj, 'click', (function(marker) {
return function() {
window.location = marker[4];
}
})(marker));
}
}
}
map.fitBounds(bounds)
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
{{ object.name }}
{{ object.address.city }}
{{ object.address.county }}
{{ object.address.postcode }}
{{ object.long_description }}

View File

@@ -0,0 +1,72 @@
{% extends "base.html" %}
{% load markdown_deux_tags %}
{% load staticfiles %}
{% block title %}
{{ chain.name }}
{% endblock %}
{% block style %}
<style type="text/css">
#map-canvas-stores {
width: 100%;
height: 400px;
}
</style>
{% endblock %}
{% block scripts %}
<script type="text/javascript" src="https://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" src="{% static "js/gmap.js" %}"></script>
<script type="text/javascript">
var stores = [
{% for store in chain.stores.all %}{% if store.address.geo_latitude %}['{{ store }}', {{ store.address.geo_latitude }}, {{ store.address.geo_longitude }}],{% endif %}
{% endfor %}
];
$(document).ready(function(){initialize_map(stores, document.getElementById('map-canvas-stores'))});
</script>
{% endblock %}
{% block content %}
<div class="page-header">
<h1>{{ chain }}</h1>
</div>
<div class="row-fluid">
<div class="span12">
{% if user.is_authenticated or not store.editor or user.is_superuser %}
<p>
{% if not chain.editor %}<a href="{% url "chain-claim" chain.slug %}" class="btn btn-small">Claim Chain</a>{% endif %}
{% if is_editor %}<a href="#" class="btn btn-small">Edit Chain</a>{% endif %}
{% if user.is_superuser %}<a href="{% url "admin:stores_chain_change" chain.pk %}" class="btn btn-small">Edit in Admin</a>{% endif %}
</p>
{% endif %}
{% if chain.website %}
<p><b>Website</b>: <a href="{{ chain.website }}" target="_blank">{{ chain.website }}</a></p>
{% endif %}
{{ chain.long_description|markdown }}
</div>
</div>
<div class="row-fluid">
<div class="span7">
<h3>Stores</h3>
<table class="table table-striped">
<tbody>
{% for store in chain.stores.all %}
<tr>
<td><a href="{% url "store-detail" store.slug %}">{{ store }}</a></td>
<td>{{ store.address.city }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="span5">
<div id="map-canvas-stores" class="map">
<noscript>
You need Javascript enabled to view the map.
</noscript>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,30 @@
{% extends "base.html" %}
{% block title %}
Chains
{% endblock %}
{% block content %}
<div class="page-header">
<h1>Chains</h1>
</div>
<div class="row-fluid">
<div class="span12">
<table class="table table-striped">
<thead>
<tr><th>Name</th><th># of Stores</th></tr>
</thead>
<tbody>
{% for chain in chain_list %}
<tr>
<td><a href="{% url "chain-detail" chain.slug %}">{{ chain }}</a></td>
<td>{{ chain.stores.count }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% include "stores/paginator.html" %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,25 @@
{% extends "base.html" %}
{% load bootstrap %}
{% block content %}
<div class="page-header">
<h1>Claim Request - {{ target_obj }}</h1>
</div>
<p>This form is to submit a request to claim a store/chain and assign it to your login. If you are a store or chain manager for {{ target_obj }} please fill in the following note with proof of your claim. Once you submit the request it'll be reviewed by {{ site.name }} staff who may need to contact you to confirm your request.</p>
<p>In the note please submit as many of the following details you can.</p>
<ul>
<li>Full name.</li>
<li>Contact phone number.</li>
<li>Email address.</li>
<li>Links to your store's website or Facebook page with the store's details listed.</li>
</ul>
<form class="form form-horizontal" method="post">
{{ form|bootstrap }}
{% csrf_token %}
<input class="btn" type="submit">
</form>
{% endblock %}

View File

@@ -0,0 +1,28 @@
{% if is_paginated %}
{% load i18n %}
<div class="pagination">
<ul>
{% if page_obj.has_previous %}
<li><a href="?page={{ page_obj.previous_page_number }}{{ getvars }}{{ hashtag }}" class="prev">&lsaquo;&lsaquo; {% trans "previous" %}</a></li>
{% else %}
<li class="disabled prev"><a href="#">&lsaquo;&lsaquo; {% trans "previous" %}</a></li>
{% endif %}
{% for page in paginator.page_range %}
{% if page %}
{% ifequal page page_obj.number %}
<li class="current page active"><a href="#">{{ page }}</a></li>
{% else %}
<li><a href="?page={{ page }}{{ getvars }}{{ hashtag }}" class="page">{{ page }}</a></li>
{% endifequal %}
{% else %}
...
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li><a href="?page={{ page_obj.next_page_number }}{{ getvars }}{{ hashtag }}" class="next">{% trans "next" %} &rsaquo;&rsaquo;</a></li>
{% else %}
<li class="disabled next"><a href="#">{% trans "next" %} &rsaquo;&rsaquo;</a></li>
{% endif %}
</ul>
</div>
{% endif %}

View File

@@ -0,0 +1,101 @@
{% extends "base.html" %}
{% load markdown_deux_tags %}
{% block title %}
{{ store.name }}
{% endblock %}
{% block style %}
<style type="text/css">
.vcard ul {
list-style-type: none;
}
#map-canvas-store {
width: 100%;
height: 300px;
}
</style>
{% endblock %}
{% block scripts %}
<script type="text/javascript" src="https://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
function initialize_map_store() {
var latlng = new google.maps.LatLng({{ store.address.geo_latitude }},{{ store.address.geo_longitude }});
var mapElem = document.getElementById("map-canvas-store");
var mapOptions = {
zoom: 16,
center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(mapElem, mapOptions);
var marker = new google.maps.Marker({
position: latlng,
map: map,
title: "{{ map.address }}"
});
}
$(document).ready(function(){initialize_map_store()});
</script>
{% endblock %}
{% block content %}
<div class="page-header">
<h1>{{ store.name }}</h1>
</div>
<div class="row-fluid">
<div class="span8">
{{ store.long_description|markdown }}
{% if store.brands.count %}
<h3>Brands Stocked</h3>
<ul>
{% for brand in store.brands.all %}
<li>{% if brand.website %}<a href="{{ brand.website }}">{{ brand }}</a>{% else %}{{ brand }}{% endif %}</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="span4">
{% if user.is_authenticated and not store.editor or user.is_superuser %}
<p>
{% if not store.editor %}<a href="{% url "store-claim" store.slug %}" class="btn btn-small">Claim Store</a>{% endif %}
{% if is_editor %}<a href="{% url "store-update" store.slug %}" class="btn btn-small">Edit Store</a>{% endif %}
{% if user.is_superuser %}<a href="{% url "admin:stores_store_change" store.pk %}" class="btn btn-small">Edit in Admin</a>{% endif %}
</p>
{% endif %}
{% if store.chain %}<p><b>Chain</b>: <a href="{% url "chain-detail" store.chain.slug %}">{{ store.chain }}</a></p>{% endif %}
<p><b>Type</b>: {{ store.get_store_type_display }}</p>
<div class="vcard">
<h3>Address</h3>
<ul class="adr">
<li class="fn">{{ store.name }}</li>
<li class="street-address">{{ store.address.address1 }}</li>
{% if store.address.address2 %}<li>{{ store.address.address2 }}</li>{% endif %}
{% if store.address.address3 %}<li>{{ store.address.address3 }}</li>{% endif %}
<li class="locality">{{ store.address.city }}</li>
<li class="region">{{ store.address.county }}</li>
<li class="postal-code">{{ store.address.postcode }}</li>
<li class="country-name">{{ store.address.country }}</li>
</ul>
<h3>Contact Details</h3>
<ul>
{% if store.website %}<li>Website: <a class="url" target="_new" href="{{ store.website }}">{{ store.website }}</a></li>{% endif %}
{% if store.email %}<li>Email: <a class="email" href="mailto:{{ store.email }}">{{ store.email }}</a></li>{% endif %}
{% if store.phone %}<li>Phone: <span class="tel">{{ store.phone }}</span></li>{% endif %}
</ul>
</div>
<div id="map-canvas-store" style="width: 300px; height: 300px;" class="map">
<noscript>
<img alt="Map of {{ store.address.address_string }}" src="https://maps.google.com/maps/api/staticmap?center={{ store.address.geo_latitude }},{{ store.address.geo_longitude }}&zoom=16&markers={{ store.address.geo_latitude }},{{ store.address.geo_longitude }}&size=300x300&sensor=false">
</noscript>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,18 @@
{% extends "base.html" %}
{% load bootstrap %}
{% block scripts %}
{{ form.media }}
{% endblock %}
{% block content %}
<div class="page-header">
<h1>Edit Store - {{ object }}</h1>
</div>
<form class="form form-horizontal" method="post">
{{ form|bootstrap }}
{% csrf_token %}
<input class="btn" type="submit">
</form>
{% endblock %}

View File

@@ -0,0 +1,76 @@
{% extends "base.html" %}
{% load staticfiles %}
{% block title %}
Stores
{% endblock %}
{% block style %}
<style type="text/css" xmlns="http://www.w3.org/1999/html">
#map-canvas-stores {
width: 100%;
height: 500px;
}
</style>
{% endblock %}
{% block scripts %}
<script type="text/javascript" src="https://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" src="{% static "js/gmap.js" %}"></script>
<script type="text/javascript">
var stores = [
{% for store in store_list %}{% if store.address.geo_latitude %}['{{ store }}', {{ store.address.geo_latitude }}, {{ store.address.geo_longitude }}, {{ store.store_type }}],{% endif %}
{% endfor %}
];
$(document).ready(function(){initialize_map(stores, document.getElementById("map-canvas-stores"))});
</script>
{% endblock %}
{% block content %}
<div class="page-header">
<h1>Stores</h1>
</div>
<div class="row-fluid">
<div class="span7">
<div class="row-fluid">
<div class="span8">
<form method="get">
<input type="text" name="q" class="search-query" placeholder="Search" value="{{ search_query }}">
<a href="#" class="btn btn-small">Advanced Search</a>
</form>
</div>
<div class="span4">
<a href="{% url "store-create" %}" class="btn btn-small pull-right">Submit A Store</a>
</div>
</div>
{% if store_list.count %}
<table class="table table-striped">
<thead>
<tr><th>Name</th><th>Town/City</th></tr>
</thead>
<tbody>
{% for store in store_list %}
<tr>
<td><a href="{% url "store-detail" store.slug %}">{{ store }}</a></td>
<td>{{ store.address.city }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% include "stores/paginator.html" %}
{% else %}
{% if search_query %}
<p>No results found for the search "{{ search_query }}".</p>
{% endif %}
{% endif %}
</div>
<div class="span5">
<div id="map-canvas-stores" class="map">
<noscript>
You need Javascript enabled to view the map.
</noscript>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,49 @@
{% extends "base.html" %}
{% load staticfiles %}
{% block title %}
Stores
{% endblock %}
{% block style %}
<style type="text/css">
#map-canvas-stores {
width: 100%;
height: 600px;
}
#geolocation {
display: none;
}
</style>
{% endblock %}
{% block scripts %}
<script type="text/javascript" src="https://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" src="{% static "js/gmap.js" %}"></script>
<script type="text/javascript" src="{% static "js/geolocation.js" %}"></script>
<script type="text/javascript">
var stores = [
{% for store in store_list %}{% if store.address.geo_latitude %}['{{ store }}', {{ store.address.geo_latitude }}, {{ store.address.geo_longitude }}, {{ store.store_type }}, '{% url "store-detail" store.slug %}'],{% endif %}
{% endfor %}
];
$(document).ready(function(){initialize_map(stores, document.getElementById('map-canvas-stores'))});
</script>
{% endblock %}
{% block content %}
<div class="jumbotron">
<p class="lead">
{{ site.name }} lists {{ store_count }} stores, and {{ chain_count }} chains across the UK and Ireland.
</p>
<p id="geolocation"><a class="btn btn-large">Find Stores Near Me.</a></p>
</div>
<div class="row-fluid">
<div class="span8 offset2">
<div id="map-canvas-stores" class="map">
<noscript>
You need Javascript enabled to view the map.
</noscript>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,73 @@
{% extends "base.html" %}
{% load staticfiles %}
{% block title %}
Stores
{% endblock %}
{% block style %}
<style type="text/css">
#map-canvas-stores {
width: 100%;
height: 500px;
}
</style>
{% endblock %}
{% block scripts %}
<script type="text/javascript" src="https://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" src="{% static "js/gmap.js" %}"></script>
<script type="text/javascript">
var stores = [
{% for store in object_list %}{% if store.object.address.geo_latitude %}['{{ store }}', {{ store.object.address.geo_latitude }}, {{ store.object.address.geo_longitude }}, {{ store.object.store_type }}, '{% url "store-detail" store.object.pk %}'],{% endif %}
{% endfor %}
{% if location_geo %}['Search Location', {{ location_geo.0 }}, {{ location_geo.1 }}, 999, ''],{% endif %}
];
$(document).ready(function(){ initialize_map(stores, document.getElementById("map-canvas-stores")); });
</script>
{% endblock %}
{% block content %}
<div class="page-header">
<h1>Stores</h1>
</div>
<div class="row-fluid">
<div class="span7">
<form method="get">
<input type="text" name="location" class="search-query" placeholder="Location" value="{{ location }}">
<select name="distance">
<option value="10">10km</option>
<option value="30">20km</option>
<option value="30">30km</option>
</select>
</form>
{% if object_list.count %}
<table class="table table-striped">
<thead>
<tr><th>Name</th><th>Town/City</th><th>Distance</th></tr>
</thead>
<tbody>
{% for store in object_list %}
<tr>
<td><a href="{% url "store-detail" store.object.slug %}">{{ store.object }}</a></td>
<td>{{ store.object.address.city }}</td>
<td>{{ store.distance }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% include "stores/paginator.html" %}
{% else %}
<p>No results found.</p>
{% endif %}
</div>
<div class="span5">
<div id="map-canvas-stores" class="map">
<noscript>
You need Javascript enabled to view the map.
</noscript>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,23 @@
{% extends "base.html" %}
{% load bootstrap %}
{% block style %}
{{ form.media }}
{% endblock %}
{% block content %}
<div class="page-header">
<h1>Add Store ({{ wizard.steps.step1 }} of {{ wizard.steps.count }})</h1>
</div>
<form class="form form-horizontal" method="post">
{{ wizard.management_form|bootstrap }}
{{ wizard.form|bootstrap }}
{% csrf_token %}
{% if wizard.steps.prev %}
<button name="wizard_goto_step" class="btn" type="submit" value="{{ wizard.steps.first }}">First Step</button>
<button name="wizard_goto_step" class="btn" type="submit" value="{{ wizard.steps.prev }}">Previous Step</button>
{% endif %}
<button name="wizard_goto_step" class="btn" type="submit" value="{{ wizard.steps.next }}">Next Step</button>
</form>
{% endblock %}

22
app/stores/urls.py Normal file
View File

@@ -0,0 +1,22 @@
from django.conf.urls import patterns, include, url
from .views import *
from .forms import AddressForm, StoreForm
from .models import Store, Chain
urlpatterns = patterns('',
url(r'^$', MapView.as_view(), name='map'),
url(r'^chains/$', ChainListView.as_view(), name='chain-list'),
url(r'^chains/(?P<slug>.*)/claim/$', ClaimCreateView.as_view(target_model=Chain), name='chain-claim'),
url(r'^chains/(?P<pk>\d+)/$', ChainDetailView.as_view(), name='chain-detail-pk'),
url(r'^chains/(?P<slug>.*)/$', ChainDetailView.as_view(), name='chain-detail'),
url(r'^stores/$', StoreListView.as_view(), name='store-list'),
url(r'^stores/create/$', StoreCreateView.as_view([AddressForm, StoreForm]), name='store-create'),
url(r'^stores/search/$', DistanceSearchView.as_view(), name='store-search'),
url(r'^stores/(?P<slug>.*)/claim/$', ClaimCreateView.as_view(target_model=Store), name='store-claim'),
url(r'^stores/(?P<slug>.*)/update/$', StoreUpdateView.as_view(), name='store-update'),
url(r'^stores/(?P<pk>\d+)/$', StoreDetailView.as_view(), name='store-detail-pk'),
url(r'^stores/(?P<slug>.*)/$', StoreDetailView.as_view(), name='store-detail'),
)

36
app/stores/utils.py Normal file
View File

@@ -0,0 +1,36 @@
from math import radians, cos, sin, asin, sqrt
from django.core.cache import cache
from geopy.geocoders import GoogleV3
def haversine(lon1, lat1, lon2, lat2):
"""
Calculate the great circle distance between two points
on the earth (specified in decimal degrees)
"""
# convert decimal degrees to radians
lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2])
# haversine formula
dlon = lon2 - lon1
dlat = lat2 - lat1
a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
c = 2 * asin(sqrt(a))
km = 6367 * c
return km
def caching_geo_lookup(address):
"""
Preforms a geo lookup against Google V3 and caches the results
"""
if not address:
return None
slug = address.lower().replace(',', '').replace(' ', '-')
geo = cache.get('geo_%s' % slug)
if not geo:
try:
geo = GoogleV3().geocode(address)
except ValueError:
return None
cache.set('geo_%s' % slug, geo, 3600)
return geo

View File

@@ -0,0 +1,5 @@
from .stores import StoreListView, StoreDetailView, StoreUpdateView, StoreCreateView
from .chains import ChainListView, ChainDetailView
from .search import DistanceSearchView
from .claims import ClaimCreateView
from .misc import MapView

View File

@@ -0,0 +1,20 @@
from django.views.generic import ListView, DetailView
from .mixins import EditorCheckMixin
from ..models import Chain
class ChainListView(ListView):
model = Chain
paginate_by = 10
def get_queryset(self):
qs = super(ChainListView, self).get_queryset()
return qs.filter(active=True).prefetch_related('stores')
class ChainDetailView(EditorCheckMixin, DetailView):
model = Chain
def get_queryset(self):
qs = super(ChainDetailView, self).get_queryset()
return qs.filter(active=True).prefetch_related('stores', 'stores__address')

View File

@@ -0,0 +1,44 @@
from django.views.generic.edit import CreateView
from django.core.urlresolvers import reverse
from django.contrib.contenttypes.models import ContentType
from django.contrib import messages
from ..forms import ClaimRequestForm
from ..models import ClaimRequest
class ClaimCreateView(CreateView):
model = ClaimRequest
target_model = None
form_class = ClaimRequestForm
template_name = 'stores/claim_form.html'
def get(self, request, *args, **kwargs):
self.target_obj = self.get_target_object()
return super(ClaimCreateView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.target_obj = self.get_target_object()
return super(ClaimCreateView, self).post(request, *args, **kwargs)
def get_target_object(self):
obj_slug = self.kwargs.get('slug')
return self.target_model.objects.get(slug=obj_slug)
def get_context_data(self, **kwargs):
ctx = super(ClaimCreateView, self).get_context_data(**kwargs)
ctx.update({
'target_obj': self.target_obj,
})
return ctx
def form_valid(self, form):
obj = form.save(commit=False)
obj.object_id = self.target_obj.pk
obj.object_type = ContentType.objects.get_for_model(self.target_model)
obj.user = self.request.user
obj.save()
messages.success(self.request, 'Your claim request for %s has been successfully submitted for review.' % self.target_obj)
return super(ClaimCreateView, self).form_valid(form)
def get_success_url(self):
return reverse('store-detail', args=[self.target_obj.slug])

32
app/stores/views/misc.py Normal file
View File

@@ -0,0 +1,32 @@
from django.core.cache import cache
from django.views.generic import ListView
from ..models import Chain, Store
class MapView(ListView):
model = Store
template_name_suffix = '_map'
def get_context_data(self, **kwargs):
ctx = super(MapView, self).get_context_data(**kwargs)
stores = cache.get('store_count')
chains = cache.get('chain_count')
if not stores:
stores = Store.objects.filter(active=True).count()
cache.set('store_count', stores, 600)
if not chains:
chains = Chain.objects.filter(active=True).count()
cache.set('chain_count', chains, 600)
ctx.update({
'store_count': stores,
'chain_count': chains,
})
return ctx
def get_queryset(self):
qs = super(MapView, self).get_queryset()
return qs.filter(active=True).select_related('address')

View File

@@ -0,0 +1,55 @@
from django.http import Http404
from haystack.query import SearchQuerySet
from haystack.inputs import AutoQuery
class EditorCheckMixin(object):
"""
A mixin to check if the object is inactive to only show it to editors or superusers
"""
def is_editor(self, object):
if self.request.user.is_superuser or self.request.user == object.editor:
return True
return False
def get_object(self, queryset=None):
obj = super(EditorCheckMixin, self).get_object(queryset)
if not obj.active:
if not self.is_editor(obj):
raise Http404
return obj
def get_context_data(self, **kwargs):
ctx = super(EditorCheckMixin, self).get_context_data(**kwargs)
ctx.update({
'is_editor': self.is_editor(self.object)
})
return ctx
class HaystackSearchListMixin(object):
"""
Adds searching via Haystack to a regular ListView
"""
search_parameter = 'q'
def get_search_terms(self):
return self.request.GET.get(self.search_parameter, None)
def get_search_filter(self):
return {
'content': AutoQuery(self.get_search_terms())
}
def haystack_search(self):
return SearchQuerySet().filter(**self.get_search_filter()).models(self.model)
def get_queryset(self):
if self.get_search_terms():
res = self.haystack_search()
if res.count() == 0:
return self.model.objects.none()
return self.model.objects.filter(pk__in=[r.object.pk for r in res.load_all()])
else:
return super(HaystackSearchListMixin, self).get_queryset()

View File

@@ -0,0 +1,42 @@
from django.views.generic import ListView
from haystack.query import SearchQuerySet
from haystack.utils.geo import Point, D
from ..models import Store
from ..utils import caching_geo_lookup
class DistanceSearchView(ListView):
template_name = 'stores/store_search.html'
distance = 25
def get_location(self):
# TODO: geopy the location based on kwargs
location = self.request.GET.get('location')
lat = self.request.GET.get('lat')
lng = self.request.GET.get('lng')
if location:
name, geo = caching_geo_lookup(location)
elif lat and lng:
name, geo = caching_geo_lookup('%s,%s' % (lat, lng))
print name
self.location_geo = geo
return Point(geo[1], geo[0])
def get_distance(self):
return D(km=self.request.GET.get('distance', self.distance))
def get_queryset(self):
location = self.get_location()
distance = self.get_distance()
print location, distance
return SearchQuerySet().dwithin('location', location, distance).distance('location', location).order_by('-distance')
def get_context_data(self, **kwargs):
ctx = super(DistanceSearchView, self).get_context_data(**kwargs)
ctx.update({
'location': self.request.GET.get('location'),
'location_geo': self.location_geo,
})
return ctx

View File

@@ -0,0 +1,68 @@
from django.views.generic import ListView, DetailView, UpdateView
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.contrib import messages
from django.contrib.formtools.wizard.views import SessionWizardView
from .mixins import EditorCheckMixin, HaystackSearchListMixin
from ..forms import AddressInline, StoreForm, AddressForm
from ..models import Store
class StoreListView(HaystackSearchListMixin, ListView):
model = Store
paginate_by = 10
def get_context_data(self, **kwargs):
ctx = super(StoreListView, self).get_context_data(**kwargs)
search = self.get_search_terms()
if search:
ctx.update({
'search_query': search,
})
return ctx
def get_queryset(self):
qs = super(StoreListView, self).get_queryset()
return qs.filter(active=True).select_related('address')
class StoreDetailView(EditorCheckMixin, DetailView):
model = Store
def get_queryset(self):
qs = super(StoreDetailView, self).get_queryset()
return qs.filter(active=True).select_related('address', 'address__county', 'address__country', 'chain').prefetch_related('brands')
class StoreUpdateView(UpdateView):
model = Store
form_class = StoreForm
def form_valid(self, form):
messages.success(self.request, "%s updated successfully." % self.object)
return super(UpdateView, self).form_valid(form)
class StoreCreateView(SessionWizardView):
form_list = [AddressForm, StoreForm]
def done(self, form_list, **kwargs):
address, store = form_list
addr_obj = address.save(commit=False)
store_obj = store.save(commit=False)
addr_obj.name = store_obj.name
addr_obj.save()
store_obj.address = addr_obj
store_obj.active = False
store_obj.save()
messages.success(self.request, "%s has been sumbitted for moderation and should be visible within the next 24 hours." % store_obj)
return HttpResponseRedirect(reverse('store-map'))
def get_template_names(self):
return 'stores/wizard/store_wizard.html'

0
app/vapemap/__init__.py Normal file
View File

142
app/vapemap/settings.py Normal file
View File

@@ -0,0 +1,142 @@
import os
from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS
DEBUG = True
TEMPLATE_DEBUG = DEBUG
ADMINS = (
# ('Your Name', 'your_email@example.com'),
)
MANAGERS = ADMINS
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(os.path.dirname(__file__), '..', '..', 'db.sqlite3'),
}
}
ALLOWED_HOSTS = [
'127.0.0.1',
]
TIME_ZONE = 'UTC'
LANGUAGE_CODE = 'en-us'
SITE_ID = 1
USE_I18N = True
USE_L10N = True
USE_TZ = True
MEDIA_ROOT = os.path.join(os.path.dirname(__file__), '..', '..', 'media')
MEDIA_URL = '/media/'
STATIC_ROOT = os.path.join(os.path.dirname(__file__), '..', '..', 'static')
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(os.path.dirname(__file__), 'static'),
]
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
)
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
)
TEMPLATE_DIRS = [
os.path.join(os.path.dirname(__file__), 'templates'),
]
SECRET_KEY = '4-jz1w*@m**o4dk6!%e22xq3aj!r^9+y(s+_8)v-+)v4fz1lsa'
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.admin',
'django.contrib.flatpages',
'south',
'markdown_deux',
'epiceditor',
'bootstrapform',
'registration',
'haystack',
'stores',
]
MIDDLEWARE_CLASSES = [
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
]
TEMPLATE_CONTEXT_PROCESSORS += (
'django.core.context_processors.request',
'stores.context_processors.site',
'stores.context_processors.pending_admin',
)
if DEBUG:
INSTALLED_APPS.append('debug_toolbar')
MIDDLEWARE_CLASSES.append('debug_toolbar.middleware.DebugToolbarMiddleware')
def custom_show_toolbar(request):
return True # Always show toolbar, for example purposes only.
DEBUG_TOOLBAR_CONFIG = {
'INTERCEPT_REDIRECTS': False,
'SHOW_TOOLBAR_CALLBACK': custom_show_toolbar,
'EXTRA_SIGNALS': [],
'HIDE_DJANGO_SQL': True,
'TAG': 'div',
'ENABLE_STACKTRACES' : True,
}
ROOT_URLCONF = 'vapemap.urls'
WSGI_APPLICATION = 'vapemap.wsgi.application'
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse'
}
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
'URL': 'http://127.0.0.1:9200/',
'INDEX_NAME': 'haystack',
},
}

View File

@@ -0,0 +1,15 @@
.epiceditor-container {
-webkit-box-shadow: inset 0 1px 1px;
-moz-box-shadow: inset 0 1px 1px;
box-shadow: inset 0 1px 1px;
-webkit-transition: border linear .2s,box-shadow linear .2s;
-moz-transition: border linear .2s,box-shadow linear .2s;
-o-transitiion: border linear .2s,box-shadow linear .2s;
transition: border linear .2s,box-shadow linear .2s;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.map label { width: auto; display:inline; }
.map img { max-width: none; }
.jumbotron { text-align: center }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

8
app/vapemap/static/js/html5shiv.js vendored Normal file
View File

@@ -0,0 +1,8 @@
/*
HTML5 Shiv v3.6.2 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
*/
(function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag();
a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x<style>article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}</style>";
c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="<xyz></xyz>";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode||
"undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup main mark meter nav output progress section summary time video",version:"3.6.2",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment();
for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d<h;d++)c.createElement(e[d]);return c}};l.html5=e;q(f)})(this,document);

View File

@@ -0,0 +1,79 @@
{% load staticfiles %}
{% load flatpages %}
{% get_flatpages as flatpages %}
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="utf-8">
<title>{{ site.name }} - {% block title %}{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<link href="{% static "css/bootstrap.min.css" %}" rel="stylesheet">
<link href="{% static "css/bootstrap-responsive.min.css" %}" rel="stylesheet">
<link href="{% static "css/base.css" %}" rel="stylesheet">
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="{% static "js/html5shiv.js" %}"></script>
<![endif]-->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript">
//
</script>
{% block style %}
{% endblock %}
</head>
<body>
<div class="container">
<div class="masthead">
<h2>{{ site.name }}</h2>
<div class="navbar">
<div class="navbar-inner">
<div class="container">
<ul class="nav">
<li><a href="{% url "map" %}">Map</a></li>
<li><a href="{% url "store-list" %}">Stores</a></li>
<li><a href="{% url "chain-list" %}">Chains</a></li>
<li><a href="#">Brands</a></li>
</ul>
<ul class="nav pull-right">
{% if not user.is_authenticated %}
<li><a href="#login-modal" data-toggle="modal">Login</a></li>
{% else %}
{% if user.is_superuser %}
{% if admin_pending_requests > 0 %}<li><a href="{% url "admin:stores_claimrequest_changelist" %}?status__exact=0"><span class="badge badge-important">{{ admin_pending_requests }}</span></a></li>{% endif %}
<li><a href="{% url "admin:index" %}">Admin</a></li>
{% endif %}
<li><a href="#">{{ user.username }}</a></li>
{% endif %}
</ul>
</div>
</div>
</div>
</div>
{% if messages %}
{% for message in messages %}
<div{% if message.tags %} class="alert alert-{{ message.tags }}"{% endif %}>
<a class="close" data-dismiss="alert" href="#">&times;</a>
{{ message }}
</div>
{% endfor %}
{% endif %}
{% block content %}
{% endblock %}
<hr/>
<div class="footer">
<p class="muted">&copy; tensixtyone 2013{% for page in flatpages %} | <a href="{{ page.url }}">{{ page.title }}</a>{% endfor %}</p>
</div>
</div>
{% if not user.is_authenticated %}
{% include "login_form.html" %}
{% endif %}
</body>
<script src="{% static "js/bootstrap.min.js" %}"></script>
{% block scripts %}
{% endblock %}
</html>

View File

@@ -0,0 +1,14 @@
{% extends "base.html" %}
{% load markdown_deux_tags %}
{% block title %}
{{ flatpage.title }}
{% endblock %}
{% block content %}
<div class="page-header">
<h1>{{ flatpage.title }}</h1>
</div>
{{ flatpage.content|markdown }}
{% endblock %}

View File

@@ -0,0 +1,31 @@
<style type="text/css" xmlns="http://www.w3.org/1999/html">
#login-modal {
text-align: center;
width: 400px;
margin-left: -200px;
}
.form-signin .form-signin-heading,
.form-signin .checkbox {
margin-bottom: 10px;
}
.form-signin input[type="text"],
.form-signin input[type="password"] {
font-size: 16px;
height: auto;
margin-bottom: 15px;
padding: 7px 9px;
}
</style>
<div id="login-modal" class="modal hide fade">
<div class="modal-body">
<form class="form-signin" action="{% url "login" %}?next={{ request.get_full_path }}" method="post">
<h2 class="form-signin-heading">Please sign in</h2>
<input name="username" type="text" class="input-block-level" placeholder="Username">
<input name="password" type="password" class="input-block-level" placeholder="Password">
{% csrf_token %}
<p><button class="btn btn-large btn-primary" type="submit" data-loading-text="Submitted...">Sign in</button></p>
<p class="muted">Don't have an account? Then <a href="{% url "registration_register" %}">sign up</a>.</p>
</form>
</div>
</div>

View File

@@ -0,0 +1,18 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
{% if account %}
<p>{% trans "Account successfully activated" %}</p>
<p><a href="{% url auth_login %}">{% trans "Log in" %}</a></p>
{% else %}
<p>{% trans "Account activation failed" %}</p>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,6 @@
{% load i18n %}
{% trans "Activate account at" %} {{ site.name }}:
http://{{ site.domain }}{% url registration_activate activation_key %}
{% blocktrans %}Link is valid for {{ expiration_days }} days.{% endblocktrans %}

View File

@@ -0,0 +1 @@
{% load i18n %}{% trans "Account activation on" %} {{ site.name }}

View File

@@ -0,0 +1,14 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<form method="post" action=".">
{{ form.as_p }}
<input type="submit" value="{% trans 'Log in' %}" />
<input type="hidden" name="next" value="{{ next }}" />
</form>
<p>{% trans "Forgot password" %}? <a href="{% url "auth_password_reset" %}">{% trans "Reset it" %}</a>!</p>
<p>{% trans "Not member" %}? <a href="{% url "registration_register" %}">{% trans "Register" %}</a>!</p>
{% endblock %}

View File

@@ -0,0 +1,6 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<p>{% trans "Logged out" %}</p>
{% endblock %}

View File

@@ -0,0 +1,6 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<p>{% trans "Password changed" %}</p>
{% endblock %}

View File

@@ -0,0 +1,10 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<form method="post" action=".">
{{ form.as_p }}
<input type="submit" value="{% trans 'Submit' %}" />
</form>
{% endblock %}

View File

@@ -0,0 +1,10 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<p>{% trans "Password reset successfully" %}</p>
<p><a href="{% url auth_login %}">{% trans "Log in" %}</a></p>
{% endblock %}

View File

@@ -0,0 +1,20 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
{% if validlink %}
<form method="post" action=".">
{{ form.as_p }}
<input type="submit" value="{% trans 'Submit' %}" />
</form>
{% else %}
<p>{% trans "Password reset failed" %}</p>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,6 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<p>{% trans "Email with password reset instructions has been sent." %}</p>
{% endblock %}

View File

@@ -0,0 +1,5 @@
{% load i18n %}
{% blocktrans %}Reset password at {{ site_name }}{% endblocktrans %}:
{% block reset_link %}
{{ protocol }}://{{ domain }}{% url auth_password_reset_confirm uidb36=uid, token=token %}
{% endblock %}

View File

@@ -0,0 +1,10 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<form method="post" action=".">
{{ form.as_p }}
<input type="submit" value="{% trans 'Submit' %}" />
</form>
{% endblock %}

View File

@@ -0,0 +1,6 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<p>{% trans "You are now registered. Activation email sent." %}</p>
{% endblock %}

View File

@@ -0,0 +1,10 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<form method="post" action=".">
{{ form.as_p }}
<input type="submit" value="{% trans 'Submit' %}" />
</form>
{% endblock %}

View File

@@ -0,0 +1,39 @@
{% extends 'base.html' %}
{% block content %}
<h2>Search</h2>
<form method="get" action=".">
<table>
{{ form.as_table }}
<tr>
<td>&nbsp;</td>
<td>
<input type="submit" value="Search">
</td>
</tr>
</table>
{% if query %}
<h3>Results</h3>
{% for result in page.object_list %}
<p>
<a href="{{ result.object.get_absolute_url }}">{{ result.object }}</a>
</p>
{% empty %}
<p>No results found.</p>
{% endfor %}
{% if page.has_previous or page.has_next %}
<div>
{% if page.has_previous %}<a href="?q={{ query }}&amp;page={{ page.previous_page_number }}">{% endif %}&laquo; Previous{% if page.has_previous %}</a>{% endif %}
|
{% if page.has_next %}<a href="?q={{ query }}&amp;page={{ page.next_page_number }}">{% endif %}Next &raquo;{% if page.has_next %}</a>{% endif %}
</div>
{% endif %}
{% else %}
{# Show some example queries to run, maybe query syntax, something else? #}
{% endif %}
</form>
{% endblock %}

14
app/vapemap/urls.py Normal file
View File

@@ -0,0 +1,14 @@
from django.conf.urls import patterns, include, url
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
url(r'user/', include('registration.backends.default.urls')),
url(r'user/', include('django.contrib.auth.urls')),
url(r'^search/', include('haystack.urls')),
url('', include('stores.urls'))
)
urlpatterns += staticfiles_urlpatterns()

32
app/vapemap/wsgi.py Normal file
View File

@@ -0,0 +1,32 @@
"""
WSGI config for vapemap project.
This module contains the WSGI application used by Django's development server
and any production WSGI deployments. It should expose a module-level variable
named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
this application via the ``WSGI_APPLICATION`` setting.
Usually you will have the standard Django WSGI application here, but it also
might make sense to replace the whole Django WSGI application with a custom one
that later delegates to the Django one. For example, you could introduce WSGI
middleware here, or combine a Django application with an application of another
framework.
"""
import os
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
# if running multiple sites in the same mod_wsgi process. To fix this, use
# mod_wsgi daemon mode with each site in its own daemon process, or use
# os.environ["DJANGO_SETTINGS_MODULE"] = "vapemap.settings"
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "vapemap.settings")
# This application object is used by any WSGI server configured to use this
# file. This includes Django's development server, if the WSGI_APPLICATION
# setting points here.
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
# Apply WSGI middleware here.
# from helloworld.wsgi import HelloWorldApplication
# application = HelloWorldApplication(application)

12
requirements.txt Normal file
View File

@@ -0,0 +1,12 @@
django>=1.5.1,<1.6
django-debug-toolbar>=0.9.4
south>=0.7.6
geopy>=0.95
django-markdown-deux>=1.0.4
django-bootstrap-form>=1.4
django-extra-views>=0.6.2
-e hg+https://bitbucket.org/ubernostrum/django-registration@b3c41b3#egg=django_registration
requests
pyelasticsearch
-e git+git://github.com/toastdriven/django-haystack.git@0e8bd20c18ce3133b3a4f285a4c420bb621ac49b#egg=django_haystack
django-epiceditor