Commits

Bruce Kroeze committed 442c560

adding optional new feature, brands

Comments (0)

Files changed (12)

 *.pyc
 satchmo/static/images/productimage-picture-*_*.jpg
+brand*translation-picture-*.jpg

satchmo/l10n/mixins.py

+# -*- coding: utf-8 -*-
+"""Provides mixin objects to ease programming for translations."""
+__docformat__="restructuredtext"
+
+from django.contrib.sites.models import Site
+from django.utils.translation import get_language
+from satchmo import caching
+import logging
+
+log = logging.getLogger('satchmo.l10n.mixins')
+
+class TranslatedObjectMixin(object):
+    """Allows any object with a "translations" object to find the proper translation.
+    """
+    
+    def _find_translation(self, language_code=None, attr='translations'):
+        """Look up a translation for an attr.
+        
+        Ex: self._find_translation(language_code='en-us', attr='translations')
+        """
+        if not language_code:
+            language_code = get_language()
+            
+        try:
+            site = Site.objects.get_current()
+            trans = caching.cache_get([self.__class__.__name__, self.id], site=site, trans=attr, lang=language_code)
+        except caching.NotCachedError, nce:
+            
+            translations = getattr(self, attr)
+
+            c = translations.filter(languagecode__exact = language_code)
+            ct = c.count()
+
+            if not c or ct == 0:
+                pos = language_code.find('-')
+                if pos>-1:
+                    short_code = language_code[:pos]
+                    #log.debug("%s: Trying to find root language content for: [%s]", self, short_code)
+                    c = translations.filter(languagecode__exact = short_code)
+                    ct = c.count()
+                    if ct>0:
+                        #log.debug("%s: Found root language content for: [%s]", self, short_code)
+                        pass
+
+            if not c or ct == 0:
+                #log.debug("Trying to find default language content for: %s", self)
+                c = translations.filter(languagecode__istartswith = settings.LANGUAGE_CODE)
+                ct = c.count()
+
+            if not c or ct == 0:
+                #log.debug("Trying to find *any* language content for: %s", self)
+                c = translations.all()
+                ct = c.count()
+
+            if ct > 0:
+                trans = c[0]
+            else:
+                trans = None
+
+            caching.cache_set(nce.key, value=trans)
+
+        return trans
+
+# class ExampleTranslation(models.Model):
+# 
+#     menu = models.ForeignKey(Example, related_name="translations")
+#     languagecode = models.CharField(_('language'), max_length=10, choices=settings.LANGUAGES)
+#     title = models.CharField(_('title'), max_length=100, blank=False)
+#     description = models.CharField(_('Description'), max_length=100, blank=True)
+# 
+#     class Meta:
+#         ordering=('languagecode', )

satchmo/local_settings-customize.py

 SITE_DOMAIN = "example.com"
 SITE_NAME = "My Site"
 
+from django.conf.urls.defaults import *
+
 SATCHMO_SETTINGS = {
     # this will override any urls set in the store url modules
     #'SHOP_URLS' : patterns('satchmo.shop.views',
     #    (r'^feed/', include('satchmo.feeds.urls')),
     #   likewise with newsletters
     #    (r'^newsletter/', include('satchmo.newsletter.urls'))
+    #   enable brands here
+    #    (r'^brand/', include('satchmo.product.brand.urls'))
+    #}
     
     # This is the base url for the shop.  Only include a leading slash
     # examples: '/shop' or '/mystore'

satchmo/product/brand/__init__.py

Empty file added.

satchmo/product/brand/admin.py

+from django.contrib import admin
+from django.utils.translation import ugettext_lazy as _
+from satchmo.product.brand.models import Brand, BrandTranslation, BrandCategory, BrandCategoryTranslation
+from satchmo.thumbnail.field import ImageWithThumbnailField
+from satchmo.thumbnail.widgets import AdminImageWithThumbnailWidget
+
+class BrandTranslation_Inline(admin.StackedInline):
+    model = BrandTranslation
+    extra = 1
+    
+    def formfield_for_dbfield(self, db_field, **kwargs):
+        # This method will turn all TextFields into giant TextFields
+        if isinstance(db_field, ImageWithThumbnailField):
+            kwargs['widget'] = AdminImageWithThumbnailWidget
+            return db_field.formfield(**kwargs)
+            
+        return super(BrandTranslation_Inline, self).formfield_for_dbfield(db_field, **kwargs)
+    
+
+class BrandCategoryTranslation_Inline(admin.StackedInline):
+    model = BrandCategoryTranslation
+    extra = 1
+
+    def formfield_for_dbfield(self, db_field, **kwargs):
+        # This method will turn all TextFields into giant TextFields
+        if isinstance(db_field, ImageWithThumbnailField):
+            kwargs['widget'] = AdminImageWithThumbnailWidget
+            return db_field.formfield(**kwargs)
+            
+        return super(BrandCategoryTranslation_Inline, self).formfield_for_dbfield(db_field, **kwargs)
+
+class BrandCategoryTranslationOptions(admin.ModelAdmin):
+    fieldsets = (
+        (None, {'fields' : ('brandcategory', 'languagecode', 'name', 'description', 'picture')}),
+    )
+
+class BrandOptions(admin.ModelAdmin):
+    inlines = [BrandTranslation_Inline]
+    filter_horizontal = ('products',)
+    
+
+class BrandTranslationOptions(admin.ModelAdmin):
+    fieldsets = (
+        (None, {'fields' : ('brand', 'languagecode', 'name', 'description', 'picture')}),
+    )
+
+class BrandCategoryOptions(admin.ModelAdmin):
+    inlines = [BrandCategoryTranslation_Inline]
+    filter_horizontal = ('products',)
+
+#admin.site.register(BrandCategoryTranslation, BrandCategoryTranslationOptions)
+admin.site.register(Brand, BrandOptions)
+#admin.site.register(BrandTranslation, BrandTranslationOptions)
+admin.site.register(BrandCategory, BrandCategoryOptions)

satchmo/product/brand/models.py

+from django.conf import settings
+from django.contrib.sites.models import Site
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+from satchmo.l10n.mixins import TranslatedObjectMixin
+from satchmo.product.models import Product
+from satchmo.thumbnail.field import ImageWithThumbnailField
+import logging
+
+log = logging.getLogger('brand.models')
+
+class BrandManager(models.Manager):
+    
+    def active(self):
+        site = Site.objects.get_current()
+        return self.filter(site=site, active=True)
+    
+    def by_slug(self, slug):
+        site = Site.objects.get_current()
+        return self.get(slug=slug, site=site)
+
+class Brand(models.Model, TranslatedObjectMixin):
+    """A product brand"""
+    site = models.ForeignKey(Site)
+    slug = models.SlugField(_("Slug"), unique=True,
+        help_text=_("Used for URLs"))
+    products = models.ManyToManyField(Product, blank=True, verbose_name=_("Products"))
+    ordering = models.IntegerField(_("Ordering"))
+    active = models.BooleanField(default=True)
+    
+    objects = BrandManager()
+    
+    def _active_categories(self):
+        return [cat for cat in self.categories.all() if cat.has_content()]
+    
+    active_categories = property(fget=_active_categories)
+    
+    def _translation(self):
+        return self._find_translation()
+    translation = property(fget=_translation)
+
+    def _get_absolute_url(self):
+        return ('satchmo_brand_view', None, {'brandname' : self.slug})
+        
+    get_absolute_url = models.permalink(_get_absolute_url)
+
+    def active_categories(self):
+        return self.categories.filter(active=True)        
+        
+    def active_products(self):
+        return self.products.filter(site=self.site, active=True)        
+
+    def has_categories(self):
+        return self.active_categories().count() > 0
+
+    def has_content(self):
+        return self.has_products() or self.has_categories()
+
+    def has_products(self):
+        return self.active_products().count > 0
+            
+    def __unicode__(self):
+        return u"Brand: %s" % self.slug
+            
+    class Meta:
+        ordering=('ordering', 'slug')
+        verbose_name = _('Brand')
+        verbose_name_plural = _('Brands')
+
+
+class BrandTranslation(models.Model):
+
+    brand = models.ForeignKey(Brand, related_name="translations")
+    languagecode = models.CharField(_('language'), max_length=10, choices=settings.LANGUAGES)
+    name = models.CharField(_('title'), max_length=100, blank=False)
+    short_description = models.CharField(_('Short Description'), blank=True, max_length=200)
+    description = models.TextField(_('Full Description'), blank=True)
+    picture = ImageWithThumbnailField(verbose_name=_('Picture'),
+        upload_to="__DYNAMIC__",
+        name_field="_filename") #Media root is automatically prepended
+    
+    def _get_filename(self):
+        if self.brand:
+            return '%s-%s' % (self.brand.slug, self.id)
+        else:
+            return 'default'
+    _filename = property(_get_filename)
+
+    class Meta:
+        ordering=('languagecode', )      
+        verbose_name = _('Brand Translation')
+        verbose_name_plural = _('Brand Translations')  
+
+class BrandCategoryManager(models.Manager):
+    def by_slug(self, brandname, slug):
+        brand = Brand.objects.by_slug(brandname)
+        return brand.categories.get(slug=slug)
+
+class BrandCategory(models.Model, TranslatedObjectMixin):
+    """A category within a brand"""
+    slug = models.SlugField(_("Slug"),
+        help_text=_("Used for URLs"))
+    brand = models.ForeignKey(Brand, related_name="categories")
+    products = models.ManyToManyField(Product, blank=True, verbose_name=_("Products"))
+    ordering = models.IntegerField(_("Ordering"))
+    active = models.BooleanField(default=True)
+
+    objects = BrandCategoryManager()
+
+    def _translation(self):
+        return self._find_translation()
+    translation = property(fget=_translation)
+
+    def _get_absolute_url(self):
+        return ('satchmo_brand_category_view', None, {'brandname' : self.brand.slug, 'catname' : self.slug})
+    
+    get_absolute_url = models.permalink(_get_absolute_url)
+        
+    def active_products(self):
+        return self.products.filter(site=self.brand.site).filter(active=True)                
+        
+    def has_categories(self):
+        return False    
+    
+    def has_content(self):
+        return self.active_products()
+
+    def has_products(self):
+        return self.active_products().count > 0
+
+    def __unicode__(self):
+        return u"BrandCategory: %s" % self.slug
+
+    class Meta:
+        ordering=('ordering', 'slug')
+        verbose_name_plural = _('Brand Categories')
+
+class BrandCategoryTranslation(models.Model):
+
+    brandcategory = models.ForeignKey(BrandCategory, related_name="translations")
+    languagecode = models.CharField(_('language'), max_length=10, choices=settings.LANGUAGES)
+    name = models.CharField(_('title'), max_length=100, blank=False)
+    short_description = models.CharField(_('Short Description'), blank=True, max_length=200)
+    description = models.TextField(_('Description'), blank=True)
+    picture = ImageWithThumbnailField(verbose_name=_('Picture'),
+        upload_to="__DYNAMIC__",
+        name_field="_filename") #Media root is automatically prepended
+    
+    def _get_filename(self):
+        if self.brandcategory:
+            return '%s-%s' % (self.brandcategory.brand.slug, self.id)
+        else:
+            return 'default'
+    _filename = property(_get_filename)
+    
+    class Meta:
+        ordering=('languagecode', )
+        verbose_name_plural = _('Brand Category Translations')
+        

satchmo/product/brand/urls.py

+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('satchmo.product.brand.views',
+    (r'^$', 'brand_list', {}, 'satchmo_brand_list'),
+    (r'^(?P<brandname>.*)/(?P<catname>.*)/$', 'brand_category_page', {}, 'satchmo_brand_category_view'),
+    (r'^(?P<brandname>.*)/$', 'brand_page', {}, 'satchmo_brand_view'),
+)
+    

satchmo/product/brand/views.py

+from django.contrib.sites.models import Site
+from django.http import HttpResponse, Http404
+from django.shortcuts import get_object_or_404, render_to_response
+from django.template import RequestContext
+from django.utils.translation import ugettext_lazy as _
+from models import Brand, BrandCategory
+from satchmo.discount.utils import find_best_auto_discount
+from satchmo.product.models import Product
+import logging
+
+log = logging.getLogger("satchmo_brand.views")
+
+def brand_list(request):
+    ctx = RequestContext(request, {
+        'brands' : Brand.objects.active(),
+    })
+    return render_to_response('product/brand/index.html', ctx)
+
+def brand_page(request, brandname):
+    try:
+        brand = Brand.objects.by_slug(brandname)
+        
+    except Brand.DoesNotExist:
+        raise Http404(_('Brand "%s" does not exist') % brandname)
+        
+    products = list(brand.active_products())
+    sale = find_best_auto_discount(products)
+    
+    ctx = RequestContext(request, {
+        'brand' : brand,
+        'sale' : sale,
+    })
+    return render_to_response('product/brand/view_brand.html', ctx)
+
+
+def brand_category_page(request, brandname, catname):
+    try:
+        cat = BrandCategory.objects.by_slug(brandname, catname)
+        
+    except Brand.DoesNotExist:
+        raise Http404(_('Brand "%s" does not exist') % brandname)
+        
+    except BrandCategory.DoesNotExist:
+        raise Http404(_('No category "%s" in brand "%s"') % (catname, brandname))
+        
+    products = list(cat.active_products())
+    sale = find_best_auto_discount(products)
+    
+    ctx = RequestContext(request, {
+        'brand' : cat,
+        'sale' : sale,
+    })
+    return render_to_response('product/brand/view_brand.html', ctx)

satchmo/settings-customize.py

     'satchmo.shop',
     'satchmo.contact',
     'satchmo.product',
+    # to use brands, uncomment this line, and also add the brand url in your satchmo_urls setting
+    # usually in local_settings.py
+    #'satchmo.product.brand'
     'satchmo.shipping',
     'satchmo.payment',
     'satchmo.discount',

satchmo/static/css/style.css

 #leftnav p, #rightnav p { margin: 0 0 1em 0; }
 #content h2 { margin: 0 0 .5em 0; }
 
+.brandcategories {
+    clear: both;
+}
+
+.brandImage,
 .productImage {
 border:solid 1px silver;
 padding:5px;
 margin-left: 10px;
 margin-right: 10px;
 margin-bottom:2px;
+text-align: center;
 }
 
 .productImage p {

satchmo/templates/product/brand/index.html

+{% extends "base.html" %}
+{% load satchmo_thumbnail i18n %}
+{% block pagename %}{% trans 'Brands' %}{% endblock %}
+
+{% block navbar %}
+<li><a href="{{shop_base}}"{% trans 'Home' %}</a></li>
+<li>{% trans 'Brands' %}</li>
+{% endblock %}
+
+{% block content %}
+<h2>Brands</h2>
+{% if brands %}
+<div id="product_category" class="brand">
+	{% for brand in brands %}{% with brand.translation as translated %}
+	<div class="brandImage">
+       	<a href="{{ brand.get_absolute_url }}">
+       		<img src="{{ translated.picture.url|thumbnail:"width=85" }}" width="85" /> 
+		</a>
+		<br/>
+       	<a href="{{ brand.get_absolute_url }}">{{ translated.name }}</a>
+	</div>
+       {% endwith %}{% endfor %}
+</div>
+{% else %}
+<p>{% trans 'No brands are active' %}</p>
+{% endif %}
+{% endblock %}

satchmo/templates/product/brand/view_brand.html

+{% extends "base.html" %}
+{% load satchmo_thumbnail i18n %}
+{% block pagename %}{{ brand.name }}{% endblock %}
+
+{% block extra-head %}
+{% if category.meta %}
+    <meta name="description" content="{{brand.name}}">
+{% endif %}
+{% endblock %}
+
+{% block navbar %}
+<li><a href="{{shop_base}}"{% trans 'Home' %}</a></li>
+<li>{{ brand.name }}</li>
+{% endblock %}
+
+{% block content %}
+{% with brand.translation as translated %}
+<div id="product_category" class="brand">
+	<div class="brand_description">
+		<h3>{{ translated.name }}</h3>
+	    {% if translated.picture %}
+	    <img src="{{ translated.picture.url|thumbnail:"width=85" }}" alt="{{ translated.name }}" />
+	    {% endif %}
+		{% if translated.description %}
+		<div class="description">
+			{{ translated.description|safe }}
+        </div>
+		{% endif %}
+		{% if user.is_staff or user.is_superuser %}
+            <p><a href="/admin/product/satchmo_brand/brand/{{ brand.id }}/">Edit this brand</a></p>
+        {% endif %}
+	</div>
+
+    {% if brand.has_content %}
+    	{% if brand.has_products %}
+		<div class="brandproducts">
+		{% for product in brand.active_products %} 
+			<div class="productImage{% if forloop.first %} first{% endif %}">	
+		        <a href="{{ product.get_absolute_url }}">
+		        	<img src="{{ product.main_image.picture.url|thumbnail:"width=85" }}" width="85" />
+				</a>
+				<br/>
+		        <a href="{{ product.get_absolute_url }}">{{ product.translated_name }}</a>
+	         </div>
+	    {% endfor %}
+		</div>
+		{% endif %}
+
+		{% if brand.has_categories %}
+		<div class="brandcategories">
+			<h4>{% trans 'View more products in these categories' %}</h4>
+      	    {% for cat in brand.active_categories %}{% with cat.translation as translatedcat %}
+			<div class="brandImage">
+            	<a href="{{ cat.get_absolute_url }}">
+            		<img src="{{ translatedcat.picture.url|thumbnail:"width=85" }}" width="85" /> 
+				</a>
+				<br/>
+            	<a href="{{ cat.get_absolute_url }}">{{ translatedcat.name }}</a>
+			</div>
+	        {% endwith %}{% endfor %}
+		</div>
+		{% endif %}
+	{% endif %}
+	{% if sale %}
+	{% include "discount/some_eligible.html" %}
+	{% endif %}
+</div>
+{% endwith %}
+{% endblock %}