├── cardiffshop
├── products
│ ├── __init__.py
│ ├── migrations
│ │ ├── __init__.py
│ │ ├── 0002_auto_20150603_2332.py
│ │ └── 0001_initial.py
│ ├── admin.py
│ ├── views.py
│ ├── constants.py
│ └── models.py
├── cardiffshop
│ ├── __init__.py
│ ├── wsgi.py
│ ├── urls.py
│ └── settings.py
├── db.sqlite3
├── static
│ └── images
│ │ └── no-image.jpg
├── uploads
│ └── products
│ │ ├── kp146.jpg
│ │ ├── Impressionist-Paintings-023.jpg
│ │ └── lavendar-field-daily-impressionist-painting.jpg
├── templates
│ └── products
│ │ └── detail.html
└── manage.py
├── requirements.txt
├── README.md
├── .gitignore
└── LICENSE
/cardiffshop/products/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/cardiffshop/cardiffshop/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/cardiffshop/products/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Django==1.8.2
2 | django-suit==0.2.13
3 | Pillow==2.8.1
--------------------------------------------------------------------------------
/cardiffshop/db.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nature613/admin-example/HEAD/cardiffshop/db.sqlite3
--------------------------------------------------------------------------------
/cardiffshop/static/images/no-image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nature613/admin-example/HEAD/cardiffshop/static/images/no-image.jpg
--------------------------------------------------------------------------------
/cardiffshop/uploads/products/kp146.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nature613/admin-example/HEAD/cardiffshop/uploads/products/kp146.jpg
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # admin-example
2 | An example project for the "Building a usable and Beautiful Admin" presentation in DjangoCon Europe 2015 Cardiff
3 |
--------------------------------------------------------------------------------
/cardiffshop/uploads/products/Impressionist-Paintings-023.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nature613/admin-example/HEAD/cardiffshop/uploads/products/Impressionist-Paintings-023.jpg
--------------------------------------------------------------------------------
/cardiffshop/uploads/products/lavendar-field-daily-impressionist-painting.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nature613/admin-example/HEAD/cardiffshop/uploads/products/lavendar-field-daily-impressionist-painting.jpg
--------------------------------------------------------------------------------
/cardiffshop/products/admin.py:
--------------------------------------------------------------------------------
1 | from products.models import Product, Category, ProductImage
2 | from django.contrib import admin
3 |
4 |
5 | admin.site.register(Category)
6 | admin.site.register(Product)
7 | admin.site.register(ProductImage)
8 |
--------------------------------------------------------------------------------
/cardiffshop/products/views.py:
--------------------------------------------------------------------------------
1 | from products.models import Product
2 | from django.views.generic import DetailView
3 |
4 |
5 | class ProductDetail(DetailView):
6 | template_name = "products/detail.html"
7 | model = Product
8 | slug_field = "slug"
9 | context_object_name = "product"
--------------------------------------------------------------------------------
/cardiffshop/templates/products/detail.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ product.name }}
6 |
7 |
8 | {{ product.name }}
9 | {{ product.description }}
10 |
11 |
--------------------------------------------------------------------------------
/cardiffshop/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cardiffshop.settings")
7 |
8 | from django.core.management import execute_from_command_line
9 |
10 | execute_from_command_line(sys.argv)
11 |
--------------------------------------------------------------------------------
/cardiffshop/products/constants.py:
--------------------------------------------------------------------------------
1 | from django.utils.translation import ugettext_lazy as _
2 |
3 | PRICE_UNIT_CHOICES = (
4 | ("GBP", _("Pounds")),
5 | ("USD", _("US Dollars")),
6 | ("EUR", _("Euro"))
7 | )
8 |
9 | CAMPAIGN_CHOICES = (
10 | ("", _("No Campaign")),
11 | ("10-percent-off", _("10% off")),
12 | ("2-for-1", _("2 for 1")),
13 | ("3-for-2", _("Buy 2 get 3")),
14 | )
--------------------------------------------------------------------------------
/cardiffshop/cardiffshop/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for cardiffshop project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cardiffshop.settings")
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/cardiffshop/cardiffshop/urls.py:
--------------------------------------------------------------------------------
1 | from products.views import ProductDetail
2 | from django.conf import settings
3 | from django.conf.urls.static import static
4 |
5 | from django.conf.urls import include, url
6 | from django.contrib import admin
7 |
8 | urlpatterns = [
9 | url(r'^admin/', include(admin.site.urls)),
10 | url(r'^products/(?P[-\w]+)/', ProductDetail.as_view(), name="product-detail"),
11 | ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) \
12 | + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | *.egg-info/
23 | .installed.cfg
24 | *.egg
25 |
26 | # PyInstaller
27 | # Usually these files are written by a python script from a template
28 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
29 | *.manifest
30 | *.spec
31 |
32 | # Installer logs
33 | pip-log.txt
34 | pip-delete-this-directory.txt
35 |
36 | # Unit test / coverage reports
37 | htmlcov/
38 | .tox/
39 | .coverage
40 | .coverage.*
41 | .cache
42 | nosetests.xml
43 | coverage.xml
44 | *,cover
45 |
46 | # Translations
47 | *.mo
48 | *.pot
49 |
50 | # Django stuff:
51 | *.log
52 |
53 | # Sphinx documentation
54 | docs/_build/
55 |
56 | # PyBuilder
57 | target/
58 |
59 | # Cardiff Shop
60 | .idea
61 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Yiğit Güler
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/cardiffshop/products/migrations/0002_auto_20150603_2332.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 | import django.utils.timezone
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('products', '0001_initial'),
12 | ]
13 |
14 | operations = [
15 | migrations.AddField(
16 | model_name='product',
17 | name='campaign',
18 | field=models.CharField(blank=True, max_length=255, verbose_name='Campaign', choices=[(b'', 'No Campaign'), (b'10-percent-off', '10% off'), (b'2-for-1', '2 for 1'), (b'3-for-2', 'Buy 2 get 3')]),
19 | ),
20 | migrations.AddField(
21 | model_name='product',
22 | name='campaign_end_date',
23 | field=models.DateField(null=True, verbose_name='Campaign End Date', blank=True),
24 | ),
25 | migrations.AddField(
26 | model_name='product',
27 | name='damaged',
28 | field=models.BooleanField(default=True, help_text=b'Only select this if all items are damaged.', verbose_name='Damaged'),
29 | ),
30 | migrations.AlterField(
31 | model_name='product',
32 | name='date_created',
33 | field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Date Created'),
34 | ),
35 | ]
36 |
--------------------------------------------------------------------------------
/cardiffshop/products/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.utils import timezone
3 | from django.utils.encoding import smart_unicode
4 | from django.utils.translation import ugettext_lazy as _
5 |
6 | from products.constants import PRICE_UNIT_CHOICES, CAMPAIGN_CHOICES
7 |
8 |
9 | class Category(models.Model):
10 | name = models.CharField(verbose_name=_("Name"), max_length=255)
11 | is_visible = models.BooleanField(verbose_name=_("Visible"), default=True)
12 |
13 | class Meta:
14 | verbose_name = _("Category")
15 | verbose_name_plural = _("Categories")
16 | ordering = ("name", )
17 |
18 | def __unicode__(self):
19 | return smart_unicode(self.name)
20 |
21 |
22 | class Product(models.Model):
23 | name = models.CharField(verbose_name=_("Name"), max_length=255)
24 | slug = models.SlugField(verbose_name=_("Slug"), max_length=255)
25 | description = models.TextField(_("Description"))
26 | category = models.ForeignKey(Category, verbose_name=_("Category"), related_name="products")
27 |
28 | campaign = models.CharField(_("Campaign"), choices=CAMPAIGN_CHOICES, blank=True, max_length=255)
29 | campaign_end_date = models.DateField(_("Campaign End Date"), blank=True, null=True)
30 |
31 | price = models.DecimalField(max_digits=10, decimal_places=2)
32 | price_unit = models.CharField(_("Price Unit"), choices=PRICE_UNIT_CHOICES, max_length=10)
33 | damaged = models.BooleanField(verbose_name=_("Damaged"), default=True,
34 | help_text="Only select this if all items are damaged.")
35 |
36 |
37 | sku_number = models.CharField(_("SKU number"), blank=True, null=False, max_length=255)
38 | barcode = models.CharField(_("Barcode"), max_length=255)
39 | stock_count = models.PositiveIntegerField(_("Stock Count"), default=0)
40 |
41 | is_visible = models.BooleanField(verbose_name=_("Visible"), default=True)
42 | date_created = models.DateTimeField(_("Date Created"), default=timezone.now)
43 |
44 | class Meta:
45 | verbose_name = _("Product")
46 | verbose_name_plural = _("Products")
47 | ordering = ("name",)
48 |
49 | def __unicode__(self):
50 | return smart_unicode(self.name)
51 |
52 |
53 | class ProductImage(models.Model):
54 | product = models.ForeignKey(Product, related_name="images")
55 | image = models.ImageField(verbose_name=_("Image"), upload_to="products/")
56 | order = models.IntegerField(verbose_name=_("Ordering"), default=0)
57 | alt_text = models.CharField(verbose_name=_("Alternative Text"), max_length=255, blank=True)
58 |
59 | class Meta:
60 | verbose_name = _("Product Image")
61 | verbose_name_plural = _("Product Images")
62 | ordering = ("order", )
--------------------------------------------------------------------------------
/cardiffshop/products/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ]
11 |
12 | operations = [
13 | migrations.CreateModel(
14 | name='Category',
15 | fields=[
16 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
17 | ('name', models.CharField(max_length=255, verbose_name='Name')),
18 | ('is_visible', models.BooleanField(default=True, verbose_name='Visible')),
19 | ],
20 | options={
21 | 'ordering': ('name',),
22 | 'verbose_name': 'Category',
23 | 'verbose_name_plural': 'Categories',
24 | },
25 | ),
26 | migrations.CreateModel(
27 | name='Product',
28 | fields=[
29 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
30 | ('name', models.CharField(max_length=255, verbose_name='Name')),
31 | ('slug', models.SlugField(max_length=255, verbose_name='Slug')),
32 | ('description', models.TextField(verbose_name='Description')),
33 | ('price', models.DecimalField(max_digits=10, decimal_places=2)),
34 | ('price_unit', models.CharField(max_length=10, verbose_name='Price Unit', choices=[(b'GBP', 'Pounds'), (b'USD', 'US Dollars'), (b'EUR', 'Euro')])),
35 | ('sku_number', models.CharField(max_length=255, verbose_name='SKU number', blank=True)),
36 | ('barcode', models.CharField(max_length=255, verbose_name='Barcode')),
37 | ('stock_count', models.PositiveIntegerField(default=0, verbose_name='Stock Count')),
38 | ('is_visible', models.BooleanField(default=True, verbose_name='Visible')),
39 | ('date_created', models.DateTimeField(auto_now=True, verbose_name='Date Created')),
40 | ('category', models.ForeignKey(related_name='products', verbose_name='Category', to='products.Category')),
41 | ],
42 | options={
43 | 'ordering': ('name',),
44 | 'verbose_name': 'Product',
45 | 'verbose_name_plural': 'Products',
46 | },
47 | ),
48 | migrations.CreateModel(
49 | name='ProductImage',
50 | fields=[
51 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
52 | ('image', models.ImageField(upload_to=b'products/', verbose_name='Image')),
53 | ('order', models.IntegerField(default=0, verbose_name='Ordering')),
54 | ('alt_text', models.CharField(max_length=255, verbose_name='Alternative Text', blank=True)),
55 | ('product', models.ForeignKey(to='products.Product')),
56 | ],
57 | options={
58 | 'ordering': ('order',),
59 | 'verbose_name': 'Product Image',
60 | 'verbose_name_plural': 'Product Images',
61 | },
62 | ),
63 | ]
64 |
--------------------------------------------------------------------------------
/cardiffshop/cardiffshop/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for cardiffshop project.
3 |
4 | Generated by 'django-admin startproject' using Django 1.8.2.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.8/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/1.8/ref/settings/
11 | """
12 |
13 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
14 | import os
15 |
16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17 |
18 | # Quick-start development settings - unsuitable for production
19 | # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
20 |
21 | # SECURITY WARNING: keep the secret key used in production secret!
22 | SECRET_KEY = 'f0hhp02n$=a*@(g-c0h1pldez3-3sa+e&g4woracd@6mkh73xl'
23 |
24 | # SECURITY WARNING: don't run with debug turned on in production!
25 | DEBUG = True
26 |
27 | ALLOWED_HOSTS = []
28 |
29 | # Application definition
30 |
31 | INSTALLED_APPS = (
32 | 'django.contrib.admin',
33 | 'django.contrib.auth',
34 | 'django.contrib.contenttypes',
35 | 'django.contrib.sessions',
36 | 'django.contrib.messages',
37 | 'django.contrib.staticfiles',
38 |
39 | 'products',
40 | )
41 |
42 | MIDDLEWARE_CLASSES = (
43 | 'django.contrib.sessions.middleware.SessionMiddleware',
44 | 'django.middleware.common.CommonMiddleware',
45 | 'django.middleware.csrf.CsrfViewMiddleware',
46 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
47 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
48 | 'django.contrib.messages.middleware.MessageMiddleware',
49 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
50 | 'django.middleware.security.SecurityMiddleware',
51 | )
52 |
53 | ROOT_URLCONF = 'cardiffshop.urls'
54 |
55 | TEMPLATES = [
56 | {
57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
58 | 'DIRS': [
59 | os.path.join(BASE_DIR, "templates")
60 | ],
61 | 'APP_DIRS': True,
62 | 'OPTIONS': {
63 | 'context_processors': [
64 | 'django.template.context_processors.debug',
65 | 'django.template.context_processors.request',
66 | 'django.contrib.auth.context_processors.auth',
67 | 'django.contrib.messages.context_processors.messages',
68 | ],
69 | },
70 | },
71 | ]
72 |
73 | WSGI_APPLICATION = 'cardiffshop.wsgi.application'
74 |
75 |
76 | # Database
77 | # https://docs.djangoproject.com/en/1.8/ref/settings/#databases
78 |
79 | DATABASES = {
80 | 'default': {
81 | 'ENGINE': 'django.db.backends.sqlite3',
82 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
83 | }
84 | }
85 |
86 |
87 | # Internationalization
88 | # https://docs.djangoproject.com/en/1.8/topics/i18n/
89 |
90 | LANGUAGE_CODE = 'en-us'
91 |
92 | TIME_ZONE = 'UTC'
93 |
94 | USE_I18N = True
95 |
96 | USE_L10N = True
97 |
98 | USE_TZ = True
99 |
100 |
101 | # Static files (CSS, JavaScript, Images)
102 | # https://docs.djangoproject.com/en/1.8/howto/static-files/
103 |
104 | STATICFILES_DIRS = (
105 | os.path.join(BASE_DIR, "static"),
106 | )
107 |
108 | STATIC_URL = '/static/'
109 |
110 |
111 | MEDIA_ROOT = os.path.join(BASE_DIR, "uploads")
112 | MEDIA_URL = '/uploads/'
113 |
--------------------------------------------------------------------------------