Add created/changed datetime support, and improve metadata on stores.

* Added schema.org metadata for stores.
This commit is contained in:
2013-04-11 18:11:58 +01:00
parent f3a2e2c8bd
commit 934062aa46
4 changed files with 259 additions and 72 deletions

View File

@@ -4,6 +4,7 @@ from django.shortcuts import render_to_response
from django import forms
from django.db import transaction
from django.db.models import Count
from django.utils.timezone import now
from django.contrib import admin
from django.contrib.contenttypes.generic import GenericStackedInline
from .models import Chain, Store, Address, Brand, ClaimRequest, Link, LinkType, County, Country
@@ -16,7 +17,7 @@ class LinkInlineAdmin(GenericStackedInline):
class ChainAdmin(admin.ModelAdmin):
list_filter = ['active']
list_display = ['name']
list_display = ['name', 'active', 'changed']
prepopulated_fields = {"slug": ("name",)}
search_fields = ['name']
inlines = [
@@ -25,7 +26,7 @@ class ChainAdmin(admin.ModelAdmin):
class StoreAdmin(admin.ModelAdmin):
list_filter = ['chain', 'active']
list_display = ['name', 'store_type', 'active']
list_display = ['name', 'store_type', 'active', 'changed']
prepopulated_fields = {"slug": ("name",)}
search_fields = ['name']
inlines = [
@@ -39,7 +40,7 @@ class StoreAdmin(admin.ModelAdmin):
def set_active(self, request, queryset):
with transaction.commit_on_success():
queryset.update(active=True)
queryset.update(active=True, changed=now())
self.message_user(request, "Successfully set %d stores to active." % queryset.count())
set_active.short_description = 'Set selected stores active.'
@@ -53,6 +54,7 @@ class StoreAdmin(admin.ModelAdmin):
with transaction.commit_on_success():
for store in queryset:
store.brands.add(brand)
queryset.update(changed=now())
if count > 1:
plural = 's'
else:
@@ -61,7 +63,7 @@ class StoreAdmin(admin.ModelAdmin):
return HttpResponseRedirect(request.get_full_path())
if not form:
form = self.AddBrandForm(initial={'_selected_action': queryset.values_list('id', flat=True)})
return render_to_response('admin/add_brand.html', {'stores': queryset, 'brand_form': form }, RequestContext(request))
return render_to_response('admin/add_brand.html', {'stores': queryset, 'brand_form': form}, RequestContext(request))
add_brand.short_description = "Add brand to the selected stores"
@@ -77,9 +79,7 @@ class StoreAdmin(admin.ModelAdmin):
count = queryset.count()
chain = form.cleaned_data['chain']
with transaction.commit_on_success():
for store in queryset:
store.chain = chain
store.save()
queryset.update(chain=chain, changed=now())
if count > 1:
plural = 's'
else:
@@ -101,12 +101,13 @@ class ClaimAdmin(admin.ModelAdmin):
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()
with transaction.commit_on_success():
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:
@@ -139,9 +140,7 @@ class CountyAdmin(admin.ModelAdmin):
if form.is_valid():
country = form.cleaned_data['country']
with transaction.commit_on_success():
for county in queryset:
county.country = country
county.save()
queryset.update(country=country)
self.message_user(request, "Successfully set %d counties to %s." % (queryset.count(), country))
return HttpResponseRedirect(request.get_full_path())
if not form:

View File

@@ -0,0 +1,172 @@
# -*- coding: utf-8 -*-
from django.utils.timezone import now
from south.db import db
from south.v2 import SchemaMigration
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Chain.created'
db.add_column(u'stores_chain', 'created',
self.gf('django.db.models.fields.DateTimeField')(default=now),
keep_default=False)
# Adding field 'Chain.changed'
db.add_column(u'stores_chain', 'changed',
self.gf('django.db.models.fields.DateTimeField')(default=now),
keep_default=False)
# Adding field 'Store.created'
db.add_column(u'stores_store', 'created',
self.gf('django.db.models.fields.DateTimeField')(default=now),
keep_default=False)
# Adding field 'Store.changed'
db.add_column(u'stores_store', 'changed',
self.gf('django.db.models.fields.DateTimeField')(default=now),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Chain.created'
db.delete_column(u'stores_chain', 'created')
# Deleting field 'Chain.changed'
db.delete_column(u'stores_chain', 'changed')
# Deleting field 'Store.created'
db.delete_column(u'stores_store', 'created')
# Deleting field 'Store.changed'
db.delete_column(u'stores_store', 'changed')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'stores.address': {
'Meta': {'object_name': 'Address'},
'address1': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'address2': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'address3': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'city': ('django.db.models.fields.CharField', [], {'max_length': "'50'"}),
'country': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'addresses'", 'to': "orm['stores.Country']"}),
'county': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'addresses'", 'null': 'True', 'to': "orm['stores.County']"}),
'geo_latitude': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
'geo_longitude': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'postcode': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'})
},
'stores.brand': {
'Meta': {'ordering': "['name']", 'object_name': 'Brand'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
},
'stores.chain': {
'Meta': {'ordering': "['name']", 'object_name': 'Chain'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'changed': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'editor': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'editable_chains'", 'null': 'True', 'to': u"orm['auth.User']"}),
'head_office': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['stores.Address']", 'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'long_description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '200', 'blank': 'True'}),
'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
},
'stores.claimrequest': {
'Meta': {'object_name': 'ClaimRequest'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'note': ('django.db.models.fields.TextField', [], {}),
'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
'object_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'claims'", 'to': u"orm['auth.User']"})
},
'stores.country': {
'Meta': {'ordering': "['name']", 'object_name': 'Country'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'iso_code': ('django.db.models.fields.CharField', [], {'max_length': '3'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'stores.county': {
'Meta': {'ordering': "['name']", 'object_name': 'County'},
'country': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'counties'", 'null': 'True', 'to': "orm['stores.Country']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'stores.link': {
'Meta': {'object_name': 'Link'},
'account_name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'account_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['stores.LinkType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
'object_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"})
},
'stores.linktype': {
'Meta': {'ordering': "['name']", 'object_name': 'LinkType'},
'icon': ('django.db.models.fields.CharField', [], {'default': "'icon-globe'", 'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'url_format': ('django.db.models.fields.CharField', [], {'max_length': '255'})
},
'stores.store': {
'Meta': {'ordering': "['name']", 'object_name': 'Store'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'address': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'stores'", 'to': "orm['stores.Address']"}),
'brands': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'stores'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['stores.Brand']"}),
'chain': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'stores'", 'null': 'True', 'to': "orm['stores.Chain']"}),
'changed': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'editor': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'editable_stores'", 'null': 'True', 'to': u"orm['auth.User']"}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'long_description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'phone': ('django.db.models.fields.CharField', [], {'max_length': '25', 'null': 'True', 'blank': 'True'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '200', 'blank': 'True'}),
'store_type': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
}
}
complete_apps = ['stores']

View File

@@ -1,6 +1,7 @@
import re
from django.db import models
from django.core.urlresolvers import reverse_lazy
from django.utils.timezone import now
from django.contrib.auth import get_user_model
from django.contrib.gis.geos import Point
from django.contrib.contenttypes.models import ContentType
@@ -21,9 +22,13 @@ class Chain(models.Model):
long_description = models.TextField('Description', null=True, blank=True)
links = generic.GenericRelation('stores.Link', content_type_field='object_type')
created = models.DateTimeField('Created Date/Time', default=now)
changed = models.DateTimeField('Changed Date/Time', default=now)
def save(self, **kwargs):
if self.slug == '':
self.slug = re.sub(r'\W+', '-', str(self.name).lower())
self.changed = now()
return super(Chain, self).save(**kwargs)
def get_absolute_url(self):
@@ -65,6 +70,9 @@ class Store(models.Model):
long_description = models.TextField('Description', null=True, blank=True, help_text="Full description of the store, including any marketing material. Markdown supported.")
brands = models.ManyToManyField('stores.Brand', related_name='stores', null=True, blank=True, help_text="Brands that are sold by this store.")
created = models.DateTimeField('Created Date/Time', default=now)
changed = models.DateTimeField('Changed Date/Time', default=now)
def get_full_address(self):
if self.address:
return self.address.full_address
@@ -76,6 +84,7 @@ class Store(models.Model):
def save(self, **kwargs):
if not self.slug or self.slug == '':
self.slug = re.sub(r'\W+', '-', str(self.name).lower())
self.changed = now()
return super(Store, self).save(**kwargs)
def get_absolute_url(self):

View File

@@ -9,7 +9,7 @@
{% block style %}
<style type="text/css" xmlns="http://www.w3.org/1999/html">
.vcard ul {
.store-details ul {
list-style-type: none;
}
#map-canvas-store {
@@ -45,69 +45,76 @@ $(document).ready(function(){initialize_map_store()});
{% endblock %}
{% block content %}
<div class="page-header">
<h1>{{ store.name }}</h1>
</div>
<div class="row-fluid">
<div class="span8">
{% if store.long_description %}
{{ store.long_description|markdown }}
{% else %}
<p>No description / write up available for this store at the moment.</p>
{% endif %}
<p>{% flag_button %}</p>
{% 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 itemscope itemtype="http://schema.org/Place">
<div class="page-header">
<h1 itemtype="name">{{ store.name }}</h1>
</div>
<div class="span4">
{% if user.is_authenticated and not store.editor or user.is_superuser %}
<p>
{% switch claim_support %}{% if not store.editor %}<a href="{% url "store-claim" store.slug %}" class="btn btn-small">Claim Store</a>{% endif %}{% endswitch %}
{% 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>
{% if store.address.county %}<li class="region">{{ store.address.county }}</li>{% endif %}
<li class="postal-code">{{ store.address.postcode }}</li>
<li class="country-name">{{ store.address.country }}</li>
</ul>
<h3>Contact Details</h3>
<div class="row-fluid">
<div class="span8">
{% if store.long_description %}
<div itemprop="description">
{{ store.long_description|markdown }}
</div>
{% else %}
<p>No description / write up available for this store at the moment.</p>
{% endif %}
<p>{% flag_button %}</p>
{% if store.brands.count %}
<h3>Brands Stocked</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 %}
{% for link in store.links.all %}
<li>{{ link.to_html|safe }}</li>
{% 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>
{% switch claim_support %}{% if not store.editor %}<a href="{% url "store-claim" store.slug %}" class="btn btn-small">Claim Store</a>{% endif %}{% endswitch %}
{% 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="store-details">
<h3>Address</h3>
<ul itemscope itemtype="http://schema.org/PostalAddress">
<li itemprop="name">{{ store.name }}</li>
<li itemprop="streetAddress">{{ 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 itemprop="addressLocality">{{ store.address.city }}</li>
{% if store.address.county %}<li itemprop="addressRegion">{{ store.address.county }}</li>{% endif %}
<li itemprop="postalCode">{{ store.address.postcode }}</li>
<li itemprop="addressCountry">{{ store.address.country }}</li>
</ul>
<div id="map-canvas-store" style="width: 300px; height: 300px;" class="map">
<noscript>
<img alt="Map of {{ store.address.full_address }}" 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>
<h3>Contact Details</h3>
<ul itemscope itemtype="http://schema.org/ContactPoint">
{% if store.website %}<li>Website: <a target="_new" href="{{ store.website }}">{{ store.website }}</a></li>{% endif %}
{% if store.email %}<li>Email: <a itemprop="email" href="mailto:{{ store.email }}">{{ store.email }}</a></li>{% endif %}
{% if store.phone %}<li>Phone: <span itemprop="telephone">{{ store.phone }}</span></li>{% endif %}
{% for link in store.links.all %}
<li>{{ link.to_html|safe }}</li>
{% endfor %}
</ul>
<div id="map-canvas-store" style="width: 300px; height: 300px;" class="map">
<noscript>
<img alt="Map of {{ store.address.full_address }}" 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>
<p class="muted">Last Updated: {{ store.changed }}</p>
<p class="hidden" itemprop="geocoordinates"><span class="latitude">{{ store.address.geo_latitude }}</span>, <span class="longitude">{{ store.address.geo_longitude }}</span></p>
</div>
</div>
{% flag_form store %}
</div>
{% flag_form store %}
</div>
{% endblock %}