app settings and dependencies refactorring and code restructure

remotes/origin/HEAD
Max Yakovenko 8 years ago
parent a3fbcbfece
commit 5ce8046928
  1. 34
      .env.sample
  2. 1
      Eshop/__init__.py
  3. 10
      Eshop/celery.py
  4. BIN
      Offer-2017-09-04.xls
  5. 1
      Procfile
  6. BIN
      Product-2017-09-05.xls
  7. 3
      cart/admin.py
  8. 5
      cart/apps.py
  9. 86
      cart/cart.py
  10. 6
      cart/context_processors.py
  11. 22
      cart/forms.py
  12. 3
      cart/models.py
  13. 3
      cart/tests.py
  14. 8
      cart/urls.py
  15. 41
      cart/views.py
  16. 0
      discount/__init__.py
  17. 10
      discount/admin.py
  18. 5
      discount/apps.py
  19. 4
      discount/forms.py
  20. 26
      discount/models.py
  21. 3
      discount/tests.py
  22. 9
      discount/urls.py
  23. 47
      discount/views.py
  24. 3
      eshop_project/__init__.py
  25. 10
      eshop_project/celery.py
  26. 178
      eshop_project/fixtures/content_types.json
  27. 0
      eshop_project/settings/__init__.py
  28. 96
      eshop_project/settings/base.py
  29. 7
      eshop_project/settings/celery.py
  30. 16
      eshop_project/settings/development.py
  31. 15
      eshop_project/settings/env.py
  32. 24
      eshop_project/settings/production.py
  33. 1
      eshop_project/tools/__init__.py
  34. 26
      eshop_project/tools/secret_gen.py
  35. 11
      eshop_project/urls.py
  36. 2
      eshop_project/wsgi.py
  37. 3049
      initial_data.json
  38. 0
      landing/__init__.py
  39. 15
      landing/admin.py
  40. 5
      landing/apps.py
  41. 8
      landing/forms.py
  42. 16
      landing/models.py
  43. 3
      landing/tests.py
  44. 7
      landing/urls.py
  45. 21
      landing/views.py
  46. 0
      loginsys/__init__.py
  47. 3
      loginsys/admin.py
  48. 5
      loginsys/apps.py
  49. 23
      loginsys/forms.py
  50. 2
      loginsys/models.py
  51. 3
      loginsys/tests.py
  52. 24
      loginsys/urls.py
  53. 44
      loginsys/views.py
  54. 2
      manage.py
  55. 0
      orders/__init__.py
  56. 95
      orders/admin.py
  57. 5
      orders/apps.py
  58. 11
      orders/context_processors.py
  59. 15
      orders/forms.py
  60. 112
      orders/models.py
  61. 43
      orders/tasks.py
  62. 3
      orders/tests.py
  63. 25
      orders/urls.py
  64. 117
      orders/views.py
  65. 5
      package.json
  66. 0
      products/__init__.py
  67. 206
      products/admin.py
  68. 5
      products/apps.py
  69. 31
      products/forms.py
  70. 169
      products/models.py
  71. 20
      products/search_indexes.py
  72. 3
      products/tests.py
  73. 33
      products/urls.py
  74. 40
      products/utils.py
  75. 79
      products/views.py
  76. 11
      ql eshop_db
  77. 66
      requirements.txt
  78. 1
      runtime.txt
  79. 971
      static/admin/css/base.ba3dc2f88fc5.css
  80. BIN
      static/admin/css/base.ba3dc2f88fc5.css.gz
  81. 971
      static/admin/css/base.css
  82. BIN
      static/admin/css/base.css.gz
  83. 342
      static/admin/css/changelists.b1b060f73d37.css
  84. BIN
      static/admin/css/changelists.b1b060f73d37.css.gz
  85. 342
      static/admin/css/changelists.css
  86. BIN
      static/admin/css/changelists.css.gz
  87. 30
      static/admin/css/dashboard.4898e2e9983d.css
  88. BIN
      static/admin/css/dashboard.4898e2e9983d.css.gz
  89. 30
      static/admin/css/dashboard.css
  90. BIN
      static/admin/css/dashboard.css.gz
  91. 20
      static/admin/css/fonts.cc6140298ba7.css
  92. BIN
      static/admin/css/fonts.cc6140298ba7.css.gz
  93. 20
      static/admin/css/fonts.css
  94. BIN
      static/admin/css/fonts.css.gz
  95. 1
      static/admin/css/forms.198ebbd0f980.css
  96. 1
      static/admin/css/forms.css
  97. 78
      static/admin/css/login.a846c0e2ef65.css
  98. BIN
      static/admin/css/login.a846c0e2ef65.css.gz
  99. 78
      static/admin/css/login.css
  100. BIN
      static/admin/css/login.css.gz
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,34 @@
# SITE SETTINGS
SITE_HOST=78.155.219.170
USE_SSL=True
ALLOWED_HOSTS=78.155.219.170,
# APP SETTINGS
SECRET_KEY=+fd(^upr^y9++w^k_p+2&k+^rtp5-um-pzvsrm9ycq++@_$5i*
# DEBUG SETTINGS
DEBUG=False
DJANGO_DEBUG_TOOLBAR=False
# DATABASE SETTINGS
DB_NAME=eshop_db
DB_USER=eshop_admin
DB_PASSWORD=12345678
DB_HOST=localhost
DB_PORT=5432
# DATABASE PRODUCTION SETTINGS
DATABASE_URL=psql://eshop_admin:eshop_password@127.0.0.1:5432/eshop
# EMAIL SETTINGS
EMAIL_BACKEND=djcelery_email.backends.CeleryEmailBackend
EMAIL_USE_TLS=True
EMAIL_USE_SSL=False
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_HOST_USER=gmail_user
EMAIL_HOST_PASSWORD=gmail_password
# CELERY SETTINGS
REDIS_CELERY_URL=redis://127.0.0.1

@ -1 +0,0 @@
from .celery import app as celery_app

@ -1,10 +0,0 @@
import os
from celery import Celery
from django.conf import settings
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Eshop.settings')
app = Celery('Eshop')
app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

Binary file not shown.

@ -1 +0,0 @@
web: gunicorn gettingstarted.wsgi --log-file -

Binary file not shown.

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

@ -1,5 +0,0 @@
from django.apps import AppConfig
class CartConfig(AppConfig):
name = 'cart'

@ -1,86 +0,0 @@
from decimal import Decimal
from django.conf import settings
from django.contrib import auth
from products.models import Product, Offer
# from discount.models import Discount
class Cart(object):
def __init__(self, request):
self.session = request.session
self.discount_id = self.session.get('discount_id')
if request.user.is_authenticated():
self.points = self.session.get('points')
self.points_quant = auth.get_user(request).profile.user_points
cart = self.session.get(settings.CART_SESSION_ID)
if not cart:
request.session['points'] = False
cart = self.session[settings.CART_SESSION_ID] = {}
self.cart = cart
def add(self, offer, price_per_itom, quantity=1, update_quantity=False):
offer_slug = offer.slug
if offer_slug not in self.cart:
self.cart[offer_slug] = {'quantity': 0,
'price': str(price_per_itom)}
if update_quantity:
self.cart[offer_slug]['quantity'] = int(quantity)
else:
self.cart[offer_slug]['quantity'] += int(quantity)
self.save()
def save(self):
self.session[settings.CART_SESSION_ID] = self.cart
self.session.modified = True
def remove(self, offer_slug):
# product_id = str(product.id)
if offer_slug in self.cart:
del self.cart[offer_slug]
self.save()
def __iter__(self):
offers_ids = self.cart.keys()
offers = Offer.objects.filter(slug__in=offers_ids)
for offer in offers:
self.cart[str(offer.slug)]['offer'] = offer
for item in self.cart.values():
item['price'] = Decimal(item['price'])
item['total_price'] = item['price'] * item['quantity']
yield item
def __len__(self):
return sum(item['quantity'] for item in self.cart.values())
def get_total_price(self):
return sum(Decimal(item['price']) * item['quantity'] for item in self.cart.values())
def clear(self):
del self.session[settings.CART_SESSION_ID]
self.session.modified = True
# @property
# def discount(self):
# if self.discount_id:
# return Discount.objects.get(id=self.discount_id)
# return None
# def get_discount(self):
# if self.discount:
# return (self.discount.discount / Decimal('100')) * self.get_total_price()
# return Decimal('0')
# def get_total_price_after_discount(self):
# return self.get_total_price() - self.get_discount()
def get_total_deduct_points(self):
total_price = self.get_total_price()
print(total_price, self.points_quant)
if total_price <= self.points_quant:
print('Less')
self.points_quant = self.points_quant - total_price + 1
return 1
print('More')
return total_price - self.points_quant

@ -1,6 +0,0 @@
from .cart import Cart
def cart(request):
return {'cart': Cart(request)}

@ -1,22 +0,0 @@
from django import forms
#PRODUCT_QUANTITY_CHOICES = [(i, str(i)) for i in range(1, 21)]
class CartAddProductForm(forms.Form):
#quantity = forms.TypedChoiceField(choices=PRODUCT_QUANTITY_CHOICES, coerce=int)
quantity = forms.CharField(required=True, widget=forms.TextInput(attrs={
'id': 'quantity',
'name': 'quantity',
'type': 'number',
'value': '0',
'onchange': 'calculate()'}))
product_slug = forms.CharField(label="product_slug", widget=forms.TextInput(attrs={
'id': 'product_slug',
'name': 'product_slug',
'type': 'hidden'}))
price_per_itom = forms.IntegerField(label="price_per_itom", widget=forms.TextInput(attrs={
'id': 'price_per_itom',
'name': 'price_per_itom',
'type': 'hidden'}))
update = forms.BooleanField(required=False, initial=False, widget=forms.HiddenInput)

@ -1,3 +0,0 @@
from django.db import models
# Create your models here.

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

@ -1,8 +0,0 @@
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.CartDetail, name='CartDetail'),
url(r'^remove/(?P<offer_slug>[-\w]+)/$', views.CartRemove, name='CartRemove'),
url(r'^add/$', views.CartAdd, name='CartAdd'),
]

@ -1,41 +0,0 @@
from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.http import require_POST
from django.views.decorators.csrf import csrf_exempt
from django.contrib import auth
from products.models import Product, Offer
from .cart import Cart
from .forms import CartAddProductForm
# from discount.forms import DiscountApllyForm
@csrf_exempt
@require_POST
def CartAdd(request):
cart = Cart(request)
form = CartAddProductForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
offer = get_object_or_404(Offer, slug=cd['product_slug'])
cart.add(offer=offer, price_per_itom=cd['price_per_itom'], quantity=cd['quantity'],
update_quantity=cd['update'])
return redirect('cart:CartDetail')
def CartRemove(request, offer_slug):
cart = Cart(request)
# offer = get_object_or_404(Offer, slug=offer_slug)
cart.remove(offer_slug)
return redirect('cart:CartDetail')
def CartDetail(request, points=False):
user = auth.get_user(request)
cart = Cart(request)
for item in cart:
item['update_quantity_form'] = CartAddProductForm(
initial={
'quantity': item['quantity'],
'product_slug': item['offer'].slug,
'price_per_itom': item['price'],
'update': True
})
# discount_apply_form = DiscountApllyForm()
return render(request, 'cart/detail.html', {'username': user.username, 'points': points})
# 'discount_apply_form': discount_apply_form})

@ -1,10 +0,0 @@
from django.contrib import admin
from .models import Discount
class DiscountAdmin(admin.ModelAdmin):
list_display = ['code', 'valid_from', 'valid_to', 'discount', 'active']
list_filter = ['valid_from', 'valid_to', 'active']
search_field = ['code']
admin.site.register(Discount, DiscountAdmin)

@ -1,5 +0,0 @@
from django.apps import AppConfig
class DiscountConfig(AppConfig):
name = 'discount'

@ -1,4 +0,0 @@
from django import forms
class DiscountApllyForm(forms.Form):
code = forms.CharField()

@ -1,26 +0,0 @@
from django.db import models
import uuid
from django.db.models.signals import post_save
from datetime import datetime, timedelta
from django.core.validators import MinValueValidator, MaxValueValidator
from django.contrib.auth.models import User
class Discount(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
code = models.CharField(max_length=50, blank=True, unique=True, default=str(uuid.uuid4()))
valid_from = models.DateTimeField(default=datetime.now, blank=True)
valid_to = models.DateTimeField(default=datetime.now()+timedelta(days=7), blank=True)
discount = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(100)], default=10)
active = models.BooleanField(default=True)
def __str__(self):
return self.code
def create_discount(sender, **kwargs):
if kwargs['created']:
user_discount = Discount.objects.create(user=kwargs['instance'])
# post_save.connect(create_discount, sender=User)
User.discount = property(lambda u: Discount.objects.get_or_create(user=u)[0])

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

@ -1,9 +0,0 @@
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^apply', views.DiscountApply, name='apply'),
url(r'^create', views.CreateDiscount, name='create'),
url(r'^points', views.PointsApply, name='points')
]

@ -1,47 +0,0 @@
import uuid
from datetime import datetime
from django.shortcuts import render, redirect
from django.views.decorators.csrf import csrf_exempt
from datetime import datetime, timedelta
from django.contrib import auth
from django.views.decorators.http import require_POST
from django.contrib.auth.decorators import login_required
from .models import Discount
from .forms import DiscountApllyForm
@login_required
@require_POST
@csrf_exempt
def PointsApply(request):
# request.session['points'] = True
return redirect('cart:CartDetail', points=True)
@require_POST
def DiscountApply(request):
now = datetime.now()
form = DiscountApllyForm(request.POST)
if form.is_valid():
code = form.cleaned_data['code']
try:
discount = Discount.objects.get(code__iexact=code,
valid_from__lte=now,
valid_to__gte=now,
active=True)
request.session['discount_id'] = discount.id
except Discount.DoesNotExist:
request.session['discount_id'] = None
return redirect('cart:CartDetail')
@login_required
@require_POST
@csrf_exempt
def CreateDiscount(request):
user = auth.get_user(request)
Discount.objects.update_or_create(user=user, defaults={'code': str(uuid.uuid4()), 'valid_from': datetime.now(),
'valid_to': datetime.now()+timedelta(days=7), 'active': True})
return redirect('profile:user_profile')

@ -0,0 +1,3 @@
from .celery import app as celery_app
__all__ = ['celery_app']

@ -0,0 +1,10 @@
import os
from celery import Celery
from django.conf import settings
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'eshop_project.settings.celery')
app = Celery('eshop_project')
app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

@ -0,0 +1,178 @@
[
{
"model": "contenttypes.contenttype",
"pk": 1,
"fields": {
"app_label": "admin",
"model": "logentry"
}
},
{
"model": "contenttypes.contenttype",
"pk": 2,
"fields": {
"app_label": "auth",
"model": "group"
}
},
{
"model": "contenttypes.contenttype",
"pk": 3,
"fields": {
"app_label": "auth",
"model": "permission"
}
},
{
"model": "contenttypes.contenttype",
"pk": 4,
"fields": {
"app_label": "auth",
"model": "user"
}
},
{
"model": "contenttypes.contenttype",
"pk": 5,
"fields": {
"app_label": "contenttypes",
"model": "contenttype"
}
},
{
"model": "contenttypes.contenttype",
"pk": 6,
"fields": {
"app_label": "sessions",
"model": "session"
}
},
{
"model": "contenttypes.contenttype",
"pk": 7,
"fields": {
"app_label": "landing",
"model": "subscriber"
}
},
{
"model": "contenttypes.contenttype",
"pk": 8,
"fields": {
"app_label": "orders",
"model": "order"
}
},
{
"model": "contenttypes.contenttype",
"pk": 9,
"fields": {
"app_label": "orders",
"model": "productsinbasket"
}
},
{
"model": "contenttypes.contenttype",
"pk": 10,
"fields": {
"app_label": "orders",
"model": "status"
}
},
{
"model": "contenttypes.contenttype",
"pk": 11,
"fields": {
"app_label": "orders",
"model": "productsinorder"
}
},
{
"model": "contenttypes.contenttype",
"pk": 12,
"fields": {
"app_label": "userprofile",
"model": "userprofile"
}
},
{
"model": "contenttypes.contenttype",
"pk": 13,
"fields": {
"app_label": "products",
"model": "productclass"
}
},
{
"model": "contenttypes.contenttype",
"pk": 14,
"fields": {
"app_label": "products",
"model": "productcategory"
}
},
{
"model": "contenttypes.contenttype",
"pk": 15,
"fields": {
"app_label": "products",
"model": "product"
}
},
{
"model": "contenttypes.contenttype",
"pk": 16,
"fields": {
"app_label": "products",
"model": "productimage"
}
},
{
"model": "contenttypes.contenttype",
"pk": 17,
"fields": {
"app_label": "products",
"model": "productattribute"
}
},
{
"model": "contenttypes.contenttype",
"pk": 18,
"fields": {
"app_label": "products",
"model": "offer"
}
},
{
"model": "contenttypes.contenttype",
"pk": 19,
"fields": {
"app_label": "products",
"model": "attributechoicevalue"
}
},
{
"model": "contenttypes.contenttype",
"pk": 20,
"fields": {
"app_label": "ipn",
"model": "paypalipn"
}
},
{
"model": "contenttypes.contenttype",
"pk": 21,
"fields": {
"app_label": "discount",
"model": "discount"
}
},
{
"model": "contenttypes.contenttype",
"pk": 22,
"fields": {
"app_label": "userprofile",
"model": "pickuprequest"
}
}
]

@ -11,25 +11,28 @@ https://docs.djangoproject.com/en/1.10/ref/settings/
"""
import os
from .env import env
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Site basic settings
SITE_HOST = env.str('SITE_HOST')
SITE_URL_HTTP = 'http://{}'.format(SITE_HOST)
SITE_URL_HTTPS = 'https://{}'.format(SITE_HOST)
DEFAULT_SITE_URL = SITE_URL_HTTPS if env.bool('USE_SSL', default=False) else SITE_URL_HTTP
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
# SECRET_KEY = '5bad1g&sjplz#xd@kz0d=ej%xw(n&_6ng#)()np9(vl)lw_h8u'
SECRET_KEY = os.environ.get('SOME_SECRET_KEY', '5bad1g&sjplz#xd@kz0d=ej%xw(n&_6ng#)()np9(vl)lw_h8u')
SECRET_KEY = env.str('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = eval(os.environ.get('DEBUG_MODE', 'True'))
TEMPLATE_DEBUG = DEBUG
ALLOWED_HOSTS = [] if DEBUG else ['78.155.219.170']
DEBUG = env.bool('DEBUG')
ALLOWED_HOSTS = tuple(env.list('ALLOWED_HOSTS', default=[]))
# Application definition
@ -42,6 +45,7 @@ INSTALLED_APPS = [
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.postgres',
'debug_toolbar',
'import_export',
'djcelery_email',
@ -49,13 +53,11 @@ INSTALLED_APPS = [
'landing',
'orders',
'loginsys',
'userprofile',
# 'haystack',
'core',
'products',
'cart',
# 'paypal.standard.ipn',
# 'payment',
'discount',
'djcelery'
]
@ -69,7 +71,7 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'Eshop.urls'
ROOT_URLCONF = 'eshop_project.urls'
TEMPLATES = [
{
@ -89,36 +91,18 @@ TEMPLATES = [
},
]
WSGI_APPLICATION = 'Eshop.wsgi.application'
WSGI_APPLICATION = 'eshop_project.wsgi.application'
MPTT_ADMIN_LEVEL_INDENT = 20
# Database
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
if DEBUG:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'eshop_db',
'USER': 'denis',
'PASSWORD': '12345678',
'HOST': 'localhost',
'PORT': '',
}
}
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'db1',
'USER': 'django_shop',
'PASSWORD': 'django_shop12345',
'HOST': 'localhost',
'PORT': '',
DATABASES = {
'extra': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR,'eshop.sqlite')
}
}
}
# Password validation
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
@ -138,7 +122,6 @@ AUTH_PASSWORD_VALIDATORS = [
},
]
# Internationalization
# https://docs.djangoproject.com/en/1.10/topics/i18n/
@ -154,7 +137,6 @@ USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.10/howto/static-files/
@ -164,31 +146,30 @@ STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static", "static_dev"),
)
STATIC_ROOT = os.path.join(BASE_DIR, "static/")#, "static_dev")
STATIC_ROOT = os.path.join(BASE_DIR, "static/") # , "static_dev")
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, "static", "media")
AUTH_PROFILE_MODULE = 'userprofile.UserProfile'
AUTH_PROFILE_MODULE = 'core.UserProfile'
CART_SESSION_ID = 'cart'
# Email
ADMINS = (
('Denis Balyasnikov', 'bda2291@mail.ru'),
)
MANAGERS = ADMINS
# Email server settings
EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
EMAIL_FILE_PATH = '/tmp/eshop-messages'
# CELERY SETTINGS
import djcelery
EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_HOST_USER = 'balyasnikovdenis22@gmail.com'
EMAIL_HOST_PASSWORD = 'ltybcbrhbcnbyf22'
djcelery.setup_loader()
FROM_EMAIL = 'notreply@russianprograms'
EMAIL_BACKEND = 'djcelery_email.backends.CeleryEmailBackend'
BROKER_URL = 'amqp://localhost'
# BROKER_URL = 'amqp://78.155.219.170'
# BROKER_USER = "django_shop"
# BROKER_PASSWORD = "django_shop12345"
# for import-export excel data
IMPORT_EXPORT_USE_TRANSACTIONS = True
@ -207,9 +188,4 @@ IMPORT_EXPORT_USE_TRANSACTIONS = True
# # 'INCLUDE_SPELLING': True,
# },
# }
import dj_database_url
db_from_env = dj_database_url.config(conn_max_age=500)
DATABASES['default'].update(db_from_env)
#STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage'
# STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage'

@ -0,0 +1,7 @@
from .base import *
CELERY_BROKER_URL= env.str('CELERY_BROKER_URL')
CELERY_RESULT_BACKEND = env.str('CELERY_RESULT_BACKEND')
CELERY_ACCEPT_CONTENT = tuple(env.list("CELERY_ACCEPT_CONTENT"))
CELERY_RESULT_SERIALIZER = env.str('json')
CELERY_TASK_SERIALIZER = env.str('json')

@ -0,0 +1,16 @@
from .base import *
DATABASES = {
**DATABASES,
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': env.str('DB_NAME'),
'USER': env.str('DB_USER'),
'PASSWORD': env.str('DB_PASSWORD'),
'HOST': env.str('DB_HOST'),
'PORT': env.str('DB_PORT')
}
}
EMAIL_BACKEND = 'djcelery_email.backends.CeleryEmailBackend'

@ -0,0 +1,15 @@
import environ
__all__ = (
'env',
)
env = environ.Env()
env.read_env(
env.path(
'ENV_FILE_PATH',
default=(environ.Path(__file__) - 3).path('.env')()
)()
)

@ -0,0 +1,24 @@
from .base import *
from .env import env
# Database settings. There must a database url
DATABASES += {
'default': env.db()
}
# Email backend settings
EMAIL_BACKEND = env.str('EMAIL_BACKEND')
EMAIL_USE_TLS = env.bool('EMAIL_USE_TLS')
EMAIL_USE_SSL = env.bool("EMAIL_USE_SSL")
EMAIL_HOST = env.str('EMAIL_HOST')
EMAIL_PORT = env.int('EMAIL_PORT')
EMAIL_HOST_USER = env.str('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = env.str('EMAIL_HOST_PASSWORD')
# Email user settings
ADMINS = (
('Dmitriy Belousov', 'dimkasp@mail.ru'),
)
MANAGERS = ADMINS
DEFAULT_FROM_EMAIL = 'notreply@russianprograms'

@ -0,0 +1,26 @@
"""
Two things are wrong with Django's default `SECRET_KEY` system:
1. It is not random but pseudo-random
2. It saves and displays the SECRET_KEY in `settings.py`
This snippet
1. uses `SystemRandom()` instead to generate a random key
2. saves a local `secret.txt`
The result is a random and safely hidden `SECRET_KEY`.
"""
try:
SECRET_KEY
except NameError as ne:
import os
from eshop_project.settings.base import BASE_DIR
SECRET_FILE = os.path.join(BASE_DIR, 'secret.txt')
try:
SECRET_KEY = open(SECRET_FILE).read().strip()
except IOError:
try:
import random
SECRET_KEY = ''.join([random.SystemRandom().choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)])
with open(SECRET_FILE,'w') as f:
f.write(SECRET_KEY)
except IOError:
Exception('Please create a %s file with random characters \
to generate your secret key!' % SECRET_FILE)

@ -13,6 +13,7 @@ Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf import settings
from django.conf.urls import url, include
from django.contrib import admin
from django.conf import settings
@ -23,14 +24,20 @@ admin.autodiscover()
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^auth/', include('loginsys.urls', namespace='auth')),
url(r'^accounts/', include('userprofile.urls', namespace='profile')),
url(r'^accounts/', include('core.urls', namespace='profile')),
url(r'^cart/', include('cart.urls', namespace='cart')),
url(r'^order/', include('orders.urls', namespace='orders')),
url(r'^discount/', include('discount.urls', namespace='discount')),
url(r'^search/', include('products.urls', namespace='products_search')),
url(r'^', include('products.urls', namespace='products')),
url(r'^', include('landing.urls')),
url(r'^landing/', include('landing.urls')),
url(r'^', include('orders.urls')),
]\
+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) \
+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if settings.DEBUG:
import debug_toolbar
urlpatterns = [
url(r'^__debug__/', include(debug_toolbar.urls))
] + urlpatterns

@ -12,7 +12,7 @@ import os
from django.core.wsgi import get_wsgi_application
# from whitenoise.django import DjangoWhiteNoise
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Eshop.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eshop_project.settings")
application = get_wsgi_application()
# application = DjangoWhiteNoise(application)

File diff suppressed because it is too large Load Diff

@ -1,15 +0,0 @@
from django.contrib import admin
from .models import *
# Register your models here.
class SubscriberAdmin(admin.ModelAdmin):
list_display = [field.name for field in Subscriber._meta.fields]
list_filter = ['name']
search_fields = ['name', 'email']
class Meta:
model = Subscriber
admin.site.register(Subscriber, SubscriberAdmin)

@ -1,5 +0,0 @@
from django.apps import AppConfig
class LandingConfig(AppConfig):
name = 'landing'

@ -1,8 +0,0 @@
from django import forms
from .models import *
class SubscriberForm(forms.ModelForm):
class Meta:
model = Subscriber
exclude = [""]

@ -1,16 +0,0 @@
from django.db import models
# Create your models here.
class Subscriber(models.Model):
email = models.EmailField()
name = models.CharField(max_length=128)
class Meta:
verbose_name = 'MySubsciber'
verbose_name_plural = 'List of Subscribers'
def __str__(self):
try:
return self.name
except:
return '{0!s}'.format(self.id)

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

@ -1,7 +0,0 @@
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^landing/', views.landing, name='landing'),
#url(r'^$', views.home, name='home'),
]

@ -1,21 +0,0 @@
from django.shortcuts import render
from .forms import SubscriberForm
from django.contrib import auth
from products.models import *
def landing(request):
form = SubscriberForm(request.POST or None)
if request.method == "POST" and form.is_valid():
print(form.cleaned_data)
form.save()
return render(request, 'landing/landing.html', locals())
def home(request):
product_images = ProductImage.objects.filter(is_active=True, is_main=True, product__is_active=True)
product_images_phones = product_images.filter(product__category__id=1)
product_images_watches = product_images.filter(product__category__id=2)
username = auth.get_user(request).username
return render(request, 'landing/home.html', locals())

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

@ -1,5 +0,0 @@
from django.apps import AppConfig
class LoginsysConfig(AppConfig):
name = 'loginsys'

@ -1,23 +0,0 @@
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
class RegistrationForm(UserCreationForm):
email = forms.EmailField(required=True)
parent = forms.CharField(required = False)
class Meta:
model = User
fields = ('username', 'email', 'password1', 'password2', 'parent')
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.email = self.cleaned_data['email']
if self.cleaned_data.get('parent'):
user.parent = User.objects.get(username=self.cleaned_data['parent'])
user.set_password(self.cleaned_data['password1'])
if commit:
user.save()
return user

@ -1,2 +0,0 @@
from django.db import models

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

@ -1,24 +0,0 @@
"""Eshop URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.10/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^login/', views.login, name='login'),
url(r'^logout/', views.logout, name='logout'),
url(r'^register/', views.register, name='register'),
]

@ -1,44 +0,0 @@
from django.shortcuts import render_to_response, redirect
from django.contrib import auth
from django.views.decorators.csrf import csrf_exempt
from .forms import RegistrationForm
@csrf_exempt
def login(request):
args = {}
if request.POST:
username = request.POST.get('username', '')
password = request.POST.get('password', '')
user = auth.authenticate(username=username, password=password)
if user is not None:
auth.login(request, user)
return redirect('/')
else:
args['login_error'] = "User is not found"
return render_to_response('login/login.html', args)
else:
return render_to_response('login/login.html', args)
def logout(request):
auth.logout(request)
return redirect('/')
@csrf_exempt
def register(request):
args= {}
args['form'] = RegistrationForm()
if request.POST:
newuser_form = RegistrationForm(request.POST)
if newuser_form.is_valid():
newuser_form.save()
newuser = auth.authenticate(username=newuser_form.cleaned_data['username'],
password=newuser_form.cleaned_data['password1'])
auth.login(request, newuser)
return redirect('/')
else:
args['form'] = newuser_form
return render_to_response('login/register.html', args)

@ -3,7 +3,7 @@ import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Eshop.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eshop_project.settings.production")
try:
from django.core.management import execute_from_command_line
except ImportError:

@ -1,95 +0,0 @@
from django.contrib import admin
from django.http import HttpResponse
from django.core.urlresolvers import reverse
from django.utils.html import format_html
from decimal import Decimal
import csv
import datetime
from django.contrib.auth.models import User
from userprofile.models import UserProfile
from .models import *
def OrderDetail(obj):
return format_html('<a href="{}">View</a>'.format(
reverse('orders:AdminOrderDetail', args=[obj.id])
))
def ExportToCSV(modeladmin, request, queryset):
opts = modeladmin.model._meta
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename=Orders-{}.csv'.format(datetime.datetime.now().strftime("%d/%m/%Y"))
writer = csv.writer(response)
fields = [field for field in opts.get_fields() if not field.many_to_many and not field.one_to_many]
writer.writerow([field.verbose_name for field in fields])
for obj in queryset:
data_row = []
for field in fields:
value = getattr(obj, field.name)
if isinstance(value, datetime.datetime):
value = value.strftime('%d/%m/%Y')
data_row.append(value)
writer.writerow(data_row)
return response
ExportToCSV.short_description = 'Export CSV'
def OrderPDF(obj):
return format_html('<a href="{}">PDF</a>'.format(
reverse('orders:AdminOrderPDF', args=[obj.id])
))
OrderPDF.short_description = 'In PDF format'
class ProductsInOrderInline(admin.TabularInline):
model = ProductsInOrder
extra = 0
class StatusAdmin(admin.ModelAdmin):
list_display = [field.name for field in Status._meta.fields]
class Meta:
model = Status
def delete_model(modeladmin, request, queryset):
for obj in queryset:
print(obj.user)
user_profile = obj.user.profile
parent_profile = user_profile.parent.profile
parent_profile.user_points += round(obj.total_price * Decimal(0.05))
parent_profile.save()
obj.delete()
delete_model.short_description = "Удалить как завершенные"
class OrderAdmin (admin.ModelAdmin):
list_display = ['id', 'customer_name', 'customer_email', 'customer_phone', 'city', 'customer_address',
'paid', 'status', 'created', 'updated', OrderDetail, OrderPDF]
list_filter = ['paid', 'created', 'updated']
inlines = [ProductsInOrderInline]
actions = [ExportToCSV, delete_model]
class Meta:
model = Order
class ProductsInOrderAdmin(admin.ModelAdmin):
list_display = [field.name for field in ProductsInOrder._meta.fields]
class Meta:
model = ProductsInOrder
class ProductsInBasketAdmin(admin.ModelAdmin):
list_display = [field.name for field in ProductsInBasket._meta.fields]
class Meta:
model = ProductsInBasket
admin.site.register(Status, StatusAdmin)
admin.site.register(Order, OrderAdmin)
admin.site.register(ProductsInOrder, ProductsInOrderAdmin)
admin.site.register(ProductsInBasket, ProductsInBasketAdmin)

@ -1,5 +0,0 @@
from django.apps import AppConfig
class OrdersConfig(AppConfig):
name = 'orders'

@ -1,11 +0,0 @@
from .models import ProductsInBasket
def getting_basket_info(request):
session_key = request.session.session_key
if not session_key:
request.session.cycle_key()
products_in_basket = ProductsInBasket.objects.filter(session_key=session_key, is_active=True)
products_total_nmb = products_in_basket.count()
return locals()

@ -1,15 +0,0 @@
from django import forms
from phonenumber_field.formfields import PhoneNumberField
from .models import Order
class OrderCreateForm(forms.ModelForm):
customer_name = forms.CharField(max_length=64, required=True, help_text='Введите Ваше полное Ф.И.О')
customer_phone = PhoneNumberField(required=True, help_text='Введите Ваш номер телефона')
customer_email = forms.EmailField(required=True, help_text='Введите Ваш e-mail')
city = forms.CharField(max_length=100, help_text='Введите Ваш город')
class Meta:
model = Order
fields = ['customer_name', 'customer_email', 'customer_phone', 'city']

@ -1,112 +0,0 @@
from django.db import models
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.dispatch import receiver
from phonenumber_field.modelfields import PhoneNumberField
# from discount.models import Discount
from decimal import Decimal
from django.core.validators import MinValueValidator, MaxValueValidator
from products.models import Product, Offer
class Status(models.Model):
name = models.CharField(max_length=16, blank=True, null=True, default=None)
is_active = models.BooleanField(default=True)
created = models.DateField(auto_now_add=True, auto_now=False)
updated = models.DateField(auto_now_add=False, auto_now=True)
def __str__(self):
return self.name
class Meta:
verbose_name = 'State of order'
verbose_name_plural = 'States'
class Order(models.Model):
user = models.ForeignKey(User, blank=True, null=True, default=None)
total_price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
customer_name = models.CharField(max_length=64, blank=True, null=True, default=None)
customer_email = models.EmailField(blank=True, null=True, default=None)
customer_phone = PhoneNumberField(blank=True, null=True, default=None)
city = models.CharField(max_length=100, blank=True, null=True, default=None)
customer_address = models.CharField(max_length=128, blank=True, null=True, default=None)
comment = models.TextField(blank=True, null=True, default=None)
status = models.ForeignKey(Status, blank=True, null=True, default=None)
created = models.DateField(auto_now_add=True, auto_now=False)
updated = models.DateField(auto_now_add=False, auto_now=True)
paid = models.BooleanField(default=False)
# discount = models.ForeignKey(Discount, related_name='orders', null=True, blank=True)
# discount_value = models.IntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(100)])
points_quant = models.IntegerField(default=0)
def __str__(self):
return "Order {!s} has status {}: ".format(self.id, self.status)
class Meta:
ordering = ('-created',)
verbose_name = 'Order'
verbose_name_plural = 'Orders'
def save(self, *args, **kwargs):
super(Order, self).save(*args, **kwargs)
class ProductsInOrder(models.Model):
order = models.ForeignKey(Order, blank=True, null=True, default=None, related_name='items')
product = models.ForeignKey(Offer, blank=True, null=True, default=None)
number = models.PositiveIntegerField(default=1)
price_per_itom = models.DecimalField(max_digits=10, decimal_places=2, default=0)
total_price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
is_active = models.BooleanField(default=True)
created = models.DateField(auto_now_add=True, auto_now=False)
updated = models.DateField(auto_now_add=False, auto_now=True)
def __str__(self):
return self.product.name
class Meta:
verbose_name = 'Product in Order'
verbose_name_plural = 'Products in Order'
def save(self, *args, **kwargs):
# self.price_per_itom = self.product.price
self.total_price = self.number * self.price_per_itom
super(ProductsInOrder, self).save(*args, **kwargs)
class ProductsInBasket(models.Model):
session_key = models.CharField(max_length=128, blank=True, null=True, default=None)
order = models.ForeignKey(Order, blank=True, null=True, default=None)
product = models.ForeignKey(Product, blank=True, null=True, default=None)
number = models.IntegerField(default=1)
price_per_itom = models.DecimalField(max_digits=10, decimal_places=2, default=0)
total_price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
is_active = models.BooleanField(default=True)
created = models.DateField(auto_now_add=True, auto_now=False)
updated = models.DateField(auto_now_add=False, auto_now=True)
def __str__(self):
return self.product.name
class Meta:
verbose_name = 'Product in Cart'
verbose_name_plural = 'Products in Cart'
def save(self, *args, **kwargs):
self.price_per_itom = self.product.price
self.total_price = int(self.number) * self.price_per_itom
super(ProductsInBasket, self).save(*args, **kwargs)
@receiver(post_save, sender=ProductsInOrder)
def product_in_order_post_save(instance,**kwargs):
order = instance.order
all_products_in_order = ProductsInOrder.objects.filter(order=order, is_active=True)
order_total_price = sum(item.total_price for item in all_products_in_order)
# if order.discount:
# order.total_price = order_total_price * (order.discount_value / Decimal('100'))
if order.points_quant:
order.total_price = order_total_price - order.points_quant
else:
order.total_price = order_total_price
order.save(force_update=True)

@ -1,43 +0,0 @@
from django.conf import settings
from celery import task
from django.template.loader import render_to_string
from django.core.mail import send_mail, EmailMessage
from io import BytesIO
import weasyprint
import pytils
from .models import Order
SUPPLIER_INFO = '''ООО "Русские Программы", ИНН 7713409230, КПП 771301001,
127411, Москва г, Дмитровское ш., дом 157, корпус 7, тел.: +74957258950'''
requisites = {'name': 'ООО "Русские Программы"', 'bank': 'АО "СМП БАНК" Г. МОСКВА', 'INN': '7713409230',
'KPP': '771301001', 'BIK': '44525503', 'bank_acc': '30101810545250000503', 'acc': '40702810300750000177',
'sup_info': SUPPLIER_INFO}
@task
def OrderCreated(order_id):
"""
Sending Email of order creating
"""
order = Order.objects.get(id=order_id)
verb_price = pytils.numeral.in_words(round(order.total_price))
verb_cur = pytils.numeral.choose_plural(round(order.total_price), ("рубль", "рубля", "рублей"))
subject = 'Order {}'.format(order.id)
message = 'Dear, {}, You have successfully placed an order.\
Your order number {}'.format(order.customer_name, order.id)
mail_send = EmailMessage(subject, message, 'admin@myshop.ru', [order.customer_email, 'bda2291@mail.ru'])
# html = render_to_string('orders:AdminOrderPDF', args=[order_id])
html = render_to_string('orders/pdf.html', {**requisites, 'order': order,
'verb_cur': verb_cur, 'verb_price': verb_price})
rendered_html = html.encode(encoding="UTF-8")
out = BytesIO()
weasyprint.HTML(string=rendered_html).write_pdf(out,
stylesheets=[weasyprint.CSS(settings.STATIC_ROOT + 'css/bootstrap.min.css')])
# weasyprint.HTML(string=rendered_html, base_url=request.build_absolute_uri()).write_pdf(response,
# stylesheets=[weasyprint.CSS(settings.STATIC_ROOT + '/css/bootstrap.min.css')])
mail_send.attach('order_{}.pdf'.format(order_id), out.getvalue(), 'application/pdf')
mail_send.send()
return mail_send

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

@ -1,25 +0,0 @@
"""Eshop URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.10/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^basket_adding/', views.basket_adding, name='basket_adding'),
url(r'^create/$', views.OrderCreate, name='OrderCreate'),
url(r'^admin/order/(?P<order_id>\d+)/$', views.AdminOrderDetail, name='AdminOrderDetail'),
url(r'^admin/order/(?P<order_id>\d+)/pdf/$', views.AdminOrderPDF, name='AdminOrderPDF')
]

@ -1,117 +0,0 @@
from django.shortcuts import render, redirect, get_object_or_404, render_to_response
from django.conf import settings
from django.contrib import auth
from django.http import HttpResponse
from django.template.loader import render_to_string, get_template
import weasyprint
import pytils
from django.core.urlresolvers import reverse
from django.http import JsonResponse
from django.contrib.admin.views.decorators import staff_member_required
from .models import ProductsInBasket, ProductsInOrder, Order
from .forms import OrderCreateForm
from .tasks import OrderCreated
from cart.cart import Cart
SUPPLIER_INFO = '''ООО "Русские Программы", ИНН 7713409230, КПП 771301001,
127411, Москва г, Дмитровское ш., дом 157, корпус 7, тел.: +74957258950'''
requisites = {'name': 'ООО "Русские Программы"', 'bank': 'АО "СМП БАНК" Г. МОСКВА', 'INN': '7713409230',
'KPP': '771301001', 'BIK': '44525503', 'bank_acc': '30101810545250000503', 'acc': '40702810300750000177',
'sup_info': SUPPLIER_INFO}
def basket_adding(request):
return_dict = {}
session_key = request.session.session_key
data = request.POST
product_id = data.get("product_id")
nmb = data.get("nmb")
new_product, created = ProductsInBasket.objects.get_or_create(session_key=session_key, product_id=product_id, defaults={'number':nmb})
if not created:
new_product.number += int(nmb)
new_product.save(force_update=True)
products_in_basket = ProductsInBasket.objects.filter(session_key=session_key, is_active=True)
products_total_nmb = products_in_basket.count()
return_dict["products_total_nmb"] = products_total_nmb
return_dict["products"] = []
for item in products_in_basket:
product_dict = {}
product_dict["id"] = item.id
product_dict["name"] = item.product.name
product_dict["price_per_item"] = item.price_per_itom
product_dict["nmb"] = item.number
return_dict["products"].append(product_dict)
return JsonResponse(return_dict)
def basket_remove(request):
return_dict = {}
session_key = request.session.session_key
data = request.POST
product_id = data.get("product_id")
def OrderCreate(request):
cart = Cart(request)
user = auth.get_user(request)
profile = user.profile
if not user.username:
return redirect('auth:login')
if request.method == 'POST':
form = OrderCreateForm(request.POST)
if form.is_valid():
order = form.save(commit=False)
order.user = user
# if cart.discount:
# order.discount = cart.discount
# order.discount_value = cart.discount.discount
if cart.points:
print(cart.points_quant)
order.points_quant = cart.points_quant
profile.user_points -= cart.points_quant
profile.save()
order.save()
for item in cart:
ProductsInOrder.objects.create(order=order, product=item['offer'],
price_per_itom=item['price'],
number=item['quantity'])
cart.clear()
# Asinc mail sending
OrderCreated.delay(order.id)
request.session['order_id'] = order.id
# return redirect(reverse('payment:process'))
return render(request, 'orders/created.html', {'username': user.username, 'order': order})
else:
return render('orders/create.html', {'username': user.username, 'cart': cart, 'form': form})
form = OrderCreateForm(instance=profile)
return render(request, 'orders/create.html', {'username': user.username, 'cart': cart, 'form': form})
@staff_member_required
def AdminOrderDetail(request, order_id):
order = get_object_or_404(Order, id=order_id)
return render(request, 'admin/orders/detail.html', {'order': order})
@staff_member_required
def AdminOrderPDF(request, order_id):
order = get_object_or_404(Order, id=order_id)
verb_price = pytils.numeral.in_words(round(order.total_price))
verb_cur = pytils.numeral.choose_plural(round(order.total_price), ("рубль", "рубля", "рублей"))
html = render_to_string('orders/pdf.html', {**requisites, 'order': order,
'verb_cur': verb_cur, 'verb_price': verb_price})
rendered_html = html.encode(encoding="UTF-8")
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = 'filename=order_{}.pdf'.format(order.id)
weasyprint.HTML(string=rendered_html, base_url=request.build_absolute_uri()).write_pdf(response,
stylesheets=[weasyprint.CSS(settings.STATIC_ROOT + '/css/bootstrap.min.css')])
return response

@ -1,5 +0,0 @@
{
"dependencies": {
"bower": "latest"
}
}

@ -1,206 +0,0 @@
from django.contrib import admin
# from mptt.admin import MPTTModelAdmin
from import_export import resources, fields, widgets
from import_export.admin import ImportExportModelAdmin
from .models import *
class CustomModelResource(resources.ModelResource):
def before_import_row(self, row, **kwargs):
"""
Override to add additional logic. Does nothing by default.
"""
try:
row['attributes'] = eval(row['attributes'])
except:
try:
row['discount_policy'] = eval(row['discount_policy'])
except:
pass
class CustomManyToManyWidget(widgets.ManyToManyWidget):
def clean(self, value, row=None, *args, **kwargs):
t1 = super(CustomManyToManyWidget, self).clean(value)
return self.model.objects.get(name=t1) if t1 else None
# class CustomForeignKeyWidget(widgets.ForeignKeyWidget):
# def clean(self, value, row=None, *args, **kwargs):
# return self.model.objects.get_or_create(name=value)[0]
# class ProductImageInline(admin.TabularInline):
# model = ProductImage
# extra = 0
# class ProductAttributeInline(admin.TabularInline):
# model = ProductAttribute
# extra = 1
# verbose_name_plural = 'ProductAttribute'
# suit_classes = 'suit-tab suit-tab-PA'
#
class AttributeChoiceValueInline(admin.TabularInline):
model = AttributeChoiceValue
# prepopulated_fields = {'slug': ('name',)}
extra = 1
verbose_name_plural = 'AttributeChoiceValue'
suit_classes = 'suit-tab suit-tab-ACV'
#
class OfferInline(admin.TabularInline):
model = Offer
extra = 1
verbose_name_plural = 'Offers'
suit_classes = 'suit-tab suit-tab-offers'
class ProductCategoryAdmin(admin.ModelAdmin):
list_display = [field.name for field in ProductCategory._meta.fields]
class Meta:
model = ProductCategory
# class AttributeChoiceValueAdmin(admin.ModelAdmin):
# list_display = [field.name for field in ProductCategory._meta.fields]
#
# class Meta:
# model = AttributeChoiceValue
#
# admin.site.register(AttributeChoiceValue, AttributeChoiceValueAdmin)
class ProductAttributeAdmin(admin.ModelAdmin):
list_display = [field.name for field in ProductAttribute._meta.fields]
inlines = [AttributeChoiceValueInline]
# prepopulated_fields = {'slug': ('name',)}
suit_form_tabs = (('general', 'General'),
('ACV', 'AttributeValues'),)
class Meta:
model = ProductAttribute
admin.site.register(ProductAttribute, ProductAttributeAdmin)
class ProducerAdmin(admin.ModelAdmin):
list_display = [field.name for field in Producer._meta.fields]
class Meta:
model = Producer
admin.site.register(Producer, ProducerAdmin)
class ProductResource(CustomModelResource):
# id = fields.Field(default=generate_Jid(prefix='J'),
# readonly=True,
# widget=widgets.CharWidget(),
# )
name = fields.Field(column_name='name', attribute='name',
default=None,
widget=widgets.CharWidget(),
)
# price = fields.Field(column_name='price', attribute='price',
# default=0,
# widget=widgets.DecimalWidget(),
# )
description = fields.Field(column_name='description', attribute='description',
default=None,
widget=widgets.CharWidget(),
)
# producer = fields.Field(column_name='producer', attribute='producer',
# default=None,
# widget=widgets.CharWidget(),
# )
category = fields.Field(column_name='category', attribute='category',
default=None,
widget=widgets.ForeignKeyWidget(ProductCategory, field='name'),
)
producer = fields.Field(column_name='producer', attribute='producer',
default=None,
widget=widgets.ForeignKeyWidget(Producer, field='name'),
)
attributes = fields.Field(column_name='attributes', attribute='attributes',
default=None,
widget=CustomManyToManyWidget(ProductAttribute, field="name"),
)
is_active = fields.Field(column_name='is_active', attribute='is_active',
default=1,
widget=widgets.BooleanWidget())
discount_policy = fields.Field(column_name='discount_policy', attribute='discount_policy',
default={},
widget=widgets.CharWidget())
# delete = fields.Field(column_name='delete', attribute='delete',
# default=0,
# widget=widgets.BooleanWidget())
# def for_delete(self, row, instance):
# return self.fields['delete'].clean(row)
class Meta:
model = Product
fields = ('id', 'name', 'description', 'producer', 'category', 'is_active', 'attributes', 'discount_policy')
export_order = ('id', 'name', 'producer', 'is_active', 'category', 'attributes', 'description', 'discount_policy')
# import_id_fields = ('name',)
def dehydrate_str_choices(self, obj):
if obj.id:
return obj.str_choices()
class ProductAdmin(ImportExportModelAdmin):
list_display = ['id', 'name', 'category', 'producer', 'is_active']
inlines = [OfferInline]
list_filter = ['is_active', 'created', 'updated', 'category']
list_editable = ['is_active']
# prepopulated_fields = {'slug': ('name',)}
search_fields = ['name', 'id']
suit_form_tabs = (('general', 'General'),
('offers', 'Offers'),)
resource_class = ProductResource
# class Meta:
# model = Product
class OfferResource(CustomModelResource):
name = fields.Field(column_name='name', attribute='name',
default=None,
widget=widgets.CharWidget(),
)
price = fields.Field(column_name='price', attribute='price',
default=0,
widget=widgets.DecimalWidget(),
)
product = fields.Field(column_name='product', attribute='product',
widget=widgets.ForeignKeyWidget(Product, field='name'),
)
is_active = fields.Field(column_name='is_active', attribute='is_active',
default=1,
widget=widgets.BooleanWidget())
attributes = fields.Field(column_name='attributes', attribute='attributes',
default={},
widget=widgets.CharWidget())
class Meta:
model = Offer
fields = ('name', 'product', 'price', 'is_active', 'attributes')
export_order = ('name', 'product', 'attributes', 'is_active', 'price')
import_id_fields = ('name',)
class OfferAdmin(ImportExportModelAdmin):
list_display = ['id', 'name', 'product', 'price', 'is_active', 'attributes']
resource_class = OfferResource
# class ProductImageAdmin(admin.ModelAdmin):
# list_display = [field.name for field in ProductImage._meta.fields]
#
# class Meta:
# model = ProductImage
# admin.site.register(ProductImage, ProductImageAdmin)
admin.site.register(ProductCategory, ProductCategoryAdmin)
admin.site.register(Product, ProductAdmin)
admin.site.register(Offer, OfferAdmin)

@ -1,5 +0,0 @@
from django.apps import AppConfig
class ProductsConfig(AppConfig):
name = 'products'

@ -1,31 +0,0 @@
from haystack.forms import FacetedSearchForm
class FacetedProductSearchForm(FacetedSearchForm):
def __init__(self, *args, **kwargs):
data = dict(kwargs.get("data", []))
self.categories = data.get('category', [])
self.producers = data.get('producer', [])
super(FacetedProductSearchForm, self).__init__(*args, **kwargs)
def search(self):
sqs = super(FacetedProductSearchForm, self).search()
if self.categories:
query = None
for category in self.categories:
if query:
query += u' OR '
else:
query = u''
query += u'"%s"' % sqs.query.clean(category)
sqs = sqs.narrow(u'category_exact:%s' % query)
if self.producers:
query = None
for producer in self.producers:
if query:
query += u' OR '
else:
query = u''
query += u'"%s"' % sqs.query.clean(producer)
sqs = sqs.narrow(u'brand_exact:%s' % query)
return sqs

@ -1,169 +0,0 @@
from django.db import models
from django.core.urlresolvers import reverse
from django.contrib.postgres.fields import HStoreField
from autoslug import AutoSlugField
import mptt
import decimal
from mptt.models import MPTTModel, TreeForeignKey
class ProductAttribute(models.Model):
name = models.CharField(max_length=64, blank=True, null=True, default=None)
slug = AutoSlugField(populate_from='name')
def __str__(self):
return self.name
class Meta:
ordering = ('slug',)
verbose_name = 'Product attribute'
verbose_name_plural = 'Product attributes'
class AttributeChoiceValue(models.Model):
name = models.CharField(max_length=64, blank=True, null=True, default=None)
slug = AutoSlugField(populate_from='name')
attribute = models.ForeignKey(ProductAttribute, on_delete=models.CASCADE, related_name='values')
def __str__(self):
return self.name
class Meta:
unique_together = ('name', 'attribute')
verbose_name = 'attribute choices value'
verbose_name_plural = 'attribute choices values'
class Producer(models.Model):
name = models.CharField(max_length=64, blank=True, null=True, default=None)
slug = AutoSlugField(populate_from='name')
image = models.ImageField(upload_to='producers/%Y/%m/%d/', blank=True, verbose_name="image of producer")
is_active = models.BooleanField(default=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('products:CategoriesListByProducer', args=[self.slug])
class Meta:
verbose_name = 'Producer'
verbose_name_plural = 'Producers'
class ProductCategory(MPTTModel):
name = models.CharField(db_index=True, unique=True, max_length=64, blank=True, null=True, default=None)
slug = AutoSlugField(populate_from='name')
is_active = models.BooleanField(default=True)
producer = models.ForeignKey(Producer, null=True, blank=True, related_name='categories')
parent = TreeForeignKey('self', null=True, blank=True, related_name='children')
image = models.ImageField(upload_to='categories/%Y/%m/%d/', blank=True, verbose_name="image of category")
# category_attributes = models.ManyToManyField(ProductAttribute, related_name='categories', blank=True)
def __str__(self):
return self.name
class Meta:
verbose_name = 'Product''s category'
verbose_name_plural = 'Category of products'
ordering = ('tree_id', 'level')
class MPTTMeta:
order_insertion_by = ['name']
def get_absolute_url(self):
return reverse('products:ProductListByCategory', args=[self.producer.slug, self.slug])
mptt.register(ProductCategory, order_insertion_py=['name'])
# class ProductClass(models.Model):
# name = models.CharField(max_length=64, blank=True, null=True, default=None)
# has_variants = models.BooleanField(default=True)
# # product_attributes = models.ManyToManyField(ProductAttribute, related_name='products_class', blank=True)
# variant_attributes = models.ManyToManyField(ProductAttribute, related_name='variants_class', blank=True)
#
# def __str__(self):
# return self.name
#
# class Meta:
# verbose_name = 'product class'
# verbose_name_plural = 'product classes'
class Product(models.Model):
name = models.CharField(max_length=64, db_index=True, blank=True, null=True, default=None)
slug = AutoSlugField(populate_from='name')
price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
# points = models.DecimalField(max_digits=8, decimal_places=2, null=True, default=0.00)
description = models.TextField(db_index=True, blank=True, null=True, default=None)
# short_description = models.TextField(blank=True, null=True, default=None)
producer = models.ForeignKey(Producer, on_delete=models.CASCADE, related_name='products')
image = models.ImageField(upload_to='products/%Y/%m/%d/', blank=True, verbose_name="image of product")
discount = models.IntegerField(blank=True, null=True, default=0)
stock = models.PositiveIntegerField(blank=True, null=True, default=0, verbose_name="In stock")
# category = TreeForeignKey(ProductCategory, blank=True, null=True, default=None, related_name='products')
category = models.ForeignKey(ProductCategory, default=None, related_name='products')
attributes = models.ManyToManyField(ProductAttribute, related_name='categories', blank=True)
discount_policy = HStoreField(blank=True, null=True, default={})
is_active = models.BooleanField(default=True)
# is_hit = models.BooleanField(default=False)
# is_new = models.BooleanField(default=False)
created = models.DateField(auto_now_add=True, auto_now=False)
updated = models.DateField(auto_now_add=False, auto_now=True)
def __str__(self):
return self.name
class Meta:
ordering = ['id']
index_together = [
['id', 'slug']
]
verbose_name = 'Product'
verbose_name_plural = 'Products'
def get_absolute_url(self):
return reverse('products:Product', args=[self.slug])
# def save(self, *args, **kwargs):
# if self.category:
# super(Product, self).save(*args, **kwargs)
#
# for cp in ProductClass.objects.filter(category=self.product_class):
# pp = ProductProperty.objects.filter(category_property=cp,
# product=self)
# if not pp:
# pp = ProductProperty(category_property=cp, product=self, value="--")
# pp.save()
# class ProductImage(models.Model):
# product = models.ForeignKey(Product, on_delete=models.CASCADE, blank=True, null=True, default=None)
# image = models.ImageField(upload_to='products_images')
# is_main = models.BooleanField(default=False)
# is_active = models.BooleanField(default=True)
# created = models.DateField(auto_now_add=True, auto_now=False)
# updated = models.DateField(auto_now_add=False, auto_now=True)
#
# def __str__(self):
# return "{!s}".format(self.id)
#
# class Meta:
# verbose_name = 'Photo'
# verbose_name_plural = 'Photos'
class Offer(models.Model):
name = models.CharField(max_length=64, blank=True, null=True, default=None)
slug = AutoSlugField(populate_from='name')
price = models.DecimalField(max_digits=8, decimal_places=2, null=True, default=0.00)
# points = models.DecimalField(max_digits=8, decimal_places=2, null=True, default=0.00)
product = models.ForeignKey(Product, on_delete=models.CASCADE, blank=True, null=True, default=None,
related_name='variants')
is_active = models.BooleanField(default=True)
attributes = HStoreField(blank=True, null=True, default={})
def __str__(self):
return self.name
class Meta:
verbose_name = 'Offer'
verbose_name_plural = 'Offers'
def save(self, *args, **kwargs):
self.points = self.price * decimal.Decimal('0.1')
super(Offer, self).save(*args, **kwargs)

@ -1,20 +0,0 @@
import datetime
from haystack import indexes
from .models import *
class ProductIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.EdgeNgramField(document=True, use_template=True, template_name="search/product_text.txt")
name = indexes.EdgeNgramField(model_attr='name')
description = indexes.EdgeNgramField(model_attr='description')
category = indexes.CharField(model_attr='category', faceted=True)
producer = indexes.CharField(model_attr='producer', faceted=True)
content_auto = indexes.EdgeNgramField(model_attr='name')
suggestions = indexes.FacetCharField()
def get_model(self):
return Product
def index_queryset(self, using=None):
return self.get_model().objects.all()

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

@ -1,33 +0,0 @@
"""Eshop URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.10/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url
from .views import productslist, product, categorieslist, producerslist
urlpatterns = [
#url(r'^product/(?P<product_id>\w+)/$', views.product, name='product'),
url(r'^$', producerslist, name='ProductList'),
# Uncomment for elasticsearch
# url(r'^autocomplete/$', autocomplete),
# url(r'^find/$', FacetedSearchView.as_view(), name='haystack_search'),
url(r'^product/(?P<product_slug>[-\w]+)/$', product, name='Product'),
url(r'^(?P<producer_slug>[-\w]+)/$', categorieslist, name='CategoriesListByProducer'),
url(r'^(?P<producer_slug>[-\w]+)/(?P<category_slug>[-\w]+)/$', productslist, name='ProductListByCategory')
]

@ -1,40 +0,0 @@
from .models import Product
def get_variant_picker_data(product):
variants = product.variants.all()
variant_attributes = product.attributes.all()
data = {'variants': [], 'variantAttributes': [], 'discount_policy': product.discount_policy}
for attribute in variant_attributes:
data['variantAttributes'].append({
'name': attribute.name,
'slug': attribute.slug,
'values': [{'name': value.name, 'slug': value.slug} for value in attribute.values.all()]
})
for variant in variants:
price = variant.price
variant_data = {
'id': variant.id,
'slug': variant.slug,
'name': variant.name,
'price': int(price),
'attributes': variant.attributes,
}
data['variants'].append(variant_data)
return data
def expand_categories(categories):
products = None
new_categories = categories
for e in categories:
if e.name.startswith('None'):
products = Product.objects.filter(category=e)
new_categories = categories.exclude(pk=e.pk)
return new_categories, products

@ -1,79 +0,0 @@
from django.shortcuts import render, render_to_response, get_object_or_404
from django.contrib import auth
from django.http import JsonResponse
import json
import decimal
from cart.forms import CartAddProductForm
from .utils import *
from cart.cart import Cart
from .models import *
# Uncomment for elasticsearch
# from .forms import FacetedProductSearchForm
# from haystack.generic_views import FacetedSearchView as BaseFacetedSearchView
# from haystack.query import SearchQuerySet
def serialize_decimal(obj):
if isinstance(obj, decimal.Decimal):
return str(obj)
return json.JSONEncoder.default(obj)
def producerslist(request):
username = auth.get_user(request).username
# category = None
# categories = ProductCategory.objects.filter(level__lte=0)
# products = Product.objects.filter(is_active=True)
producers = Producer.objects.filter(is_active=True)
# if category_slug:
# category = get_object_or_404(ProductCategory, slug=category_slug)
# products = products.filter(category__in=category.get_descendants(include_self=True))
return render(request, 'products/list.html', locals())
def categorieslist(request, producer_slug):
username = auth.get_user(request).username
producer = Producer.objects.get(slug=producer_slug)
_categories = ProductCategory.objects.filter(is_active=True, producer=producer)
categories, products = expand_categories(_categories)
return render(request, 'products/categorieslist.html', {'username': username, 'categories':categories,
'products': products})
def productslist(request, producer_slug, category_slug):
username = auth.get_user(request).username
category = ProductCategory.objects.get(slug=category_slug)
products = Product.objects.filter(is_active=True, category=category)
return render(request, 'products/productslist.html', locals())
def product(request, product_slug):
username = auth.get_user(request).username
product = get_object_or_404(Product, slug=product_slug, is_active=True)
cart_product_form = CartAddProductForm()
variant_picker_data = get_variant_picker_data(product)
show_variant_picker = all([v.attributes for v in product.variants.all()])
# session_key = request.session.session_key
# if not session_key:
# request.session.cycle_key()
return render(request, 'products/product.html', {'username': username, 'product': product, 'form': cart_product_form,
'show_variant_picker': show_variant_picker,
'variant_picker_data': variant_picker_data,
})
# Uncomment for elasticsearch
# def autocomplete(request):
# sqs = SearchQuerySet().autocomplete(content_auto=request.GET.get('query', ''))[:5]
# s = []
# for result in sqs:
# print(result)
# d = {"value": result.name, "data": result.object.slug}
# s.append(d)
# output = {'suggestions': s}
# return JsonResponse(output)
#
# class FacetedSearchView(BaseFacetedSearchView):
# form_class = FacetedProductSearchForm
# facet_fields = ['category', 'producer']
# template_name = 'search/search.html'
# paginate_by = 3
# context_object_name = 'object_list'

@ -1,11 +0,0 @@
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+----------+----------+-------------+-------------+-----------------------
eshop_db | denis | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
(4 rows)

@ -1,17 +1,59 @@
Django==1.10.6
celery==4.0.2
amqp==2.3.2
anyjson==0.3.3
appdirs==1.4.3
Babel==2.6.0
billiard==3.5.0.2
cairocffi==0.8.1
CairoSVG==2.1.3
celery==4.0.0
cffi==1.10.0
cssselect==1.0.3
cssselect2==0.2.1
defusedxml==0.5.0
diff-match-patch==20121119
dj-database-url==0.4.2
setuptools==35.0.2
django-suit==0.2.25
psycopg2==2.7.1
Django==2.0.7
django-appconf==1.0.2
django-autoslug-iplweb==1.9.4.dev0
django-celery==3.2.2
django-celery-email==2.0.0
django-debug-toolbar==1.9.1
django-environ==0.4.5
django-haystack==2.5.1
django-import-export==0.5.1
django-mptt==0.8.7
django-haystack==2.5.1
django-phonenumber-field==1.3.0pip
WeasyPrint==0.36
cffi-1.10.0
WeasyPrint==0.36
django-mptt-urls==2.0.3
django-phonenumber-field==1.3.0
django-suit==0.2.25
elasticsearch==5.0.1
whitenoise==3.3.0
et-xmlfile==1.0.1
gunicorn==19.7.1
html5lib==1.0.1
jdcal==1.4
kombu==4.2.1
lxml==4.2.3
odfpy==1.3.6
openpyxl==2.5.4
packaging==17.1
phonenumberslite==8.9.9
Pillow==5.2.0
psycopg2-binary==2.7.5
pycparser==2.18
pyparsing==2.2.0
Pyphen==0.9.4
pytils==0.3
pytz==2018.5
PyYAML==3.13
six==1.11.0
sqlparse==0.2.4
tablib==0.12.1
tinycss==0.4
tinycss2==0.6.1
unicodecsv==0.14.1
urllib3==1.23
vine==1.1.4
WeasyPrint==0.36
webencodings==0.5.1
whitenoise==3.3.0
xlrd==1.1.0
xlwt==1.3.0

@ -1 +0,0 @@
python-3.5.2

@ -1,971 +0,0 @@
/*
DJANGO Admin styles
*/
@import url("fonts.cc6140298ba7.css");
body {
margin: 0;
padding: 0;
font-size: 14px;
font-family: "Roboto","Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif;
color: #333;
background: #fff;
}
/* LINKS */
a:link, a:visited {
color: #447e9b;
text-decoration: none;
}
a:focus, a:hover {
color: #036;
}
a:focus {
text-decoration: underline;
}
a img {
border: none;
}
a.section:link, a.section:visited {
color: #fff;
text-decoration: none;
}
a.section:focus, a.section:hover {
text-decoration: underline;
}
/* GLOBAL DEFAULTS */
p, ol, ul, dl {
margin: .2em 0 .8em 0;
}
p {
padding: 0;
line-height: 140%;
}
h1,h2,h3,h4,h5 {
font-weight: bold;
}
h1 {
margin: 0 0 20px;
font-weight: 300;
font-size: 20px;
color: #666;
}
h2 {
font-size: 16px;
margin: 1em 0 .5em 0;
}
h2.subhead {
font-weight: normal;
margin-top: 0;
}
h3 {
font-size: 14px;
margin: .8em 0 .3em 0;
color: #666;
font-weight: bold;
}
h4 {
font-size: 12px;
margin: 1em 0 .8em 0;
padding-bottom: 3px;
}
h5 {
font-size: 10px;
margin: 1.5em 0 .5em 0;
color: #666;
text-transform: uppercase;
letter-spacing: 1px;
}
ul li {
list-style-type: square;
padding: 1px 0;
}
li ul {
margin-bottom: 0;
}
li, dt, dd {
font-size: 13px;
line-height: 20px;
}
dt {
font-weight: bold;
margin-top: 4px;
}
dd {
margin-left: 0;
}
form {
margin: 0;
padding: 0;
}
fieldset {
margin: 0;
padding: 0;
border: none;
border-top: 1px solid #eee;
}
blockquote {
font-size: 11px;
color: #777;
margin-left: 2px;
padding-left: 10px;
border-left: 5px solid #ddd;
}
code, pre {
font-family: "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace;
color: #666;
font-size: 12px;
}
pre.literal-block {
margin: 10px;
background: #eee;
padding: 6px 8px;
}
code strong {
color: #930;
}
hr {
clear: both;
color: #eee;
background-color: #eee;
height: 1px;
border: none;
margin: 0;
padding: 0;
font-size: 1px;
line-height: 1px;
}
/* TEXT STYLES & MODIFIERS */
.small {
font-size: 11px;
}
.tiny {
font-size: 10px;
}
p.tiny {
margin-top: -2px;
}
.mini {
font-size: 10px;
}
p.mini {
margin-top: -3px;
}
.help, p.help, form p.help {
font-size: 11px;
color: #999;
}
.help-tooltip {
cursor: help;
}
p img, h1 img, h2 img, h3 img, h4 img, td img {
vertical-align: middle;
}
.quiet, a.quiet:link, a.quiet:visited {
color: #999;
font-weight: normal;
}
.float-right {
float: right;
}
.float-left {
float: left;
}
.clear {
clear: both;
}
.align-left {
text-align: left;
}
.align-right {
text-align: right;
}
.example {
margin: 10px 0;
padding: 5px 10px;
background: #efefef;
}
.nowrap {
white-space: nowrap;
}
/* TABLES */
table {
border-collapse: collapse;
border-color: #ccc;
}
td, th {
font-size: 13px;
line-height: 16px;
border-bottom: 1px solid #eee;
vertical-align: top;
padding: 8px;
font-family: "Roboto", "Lucida Grande", Verdana, Arial, sans-serif;
}
th {
font-weight: 600;
text-align: left;
}
thead th,
tfoot td {
color: #666;
padding: 5px 10px;
font-size: 11px;
background: #fff;
border: none;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
}
tfoot td {
border-bottom: none;
border-top: 1px solid #eee;
}
thead th.required {
color: #000;
}
tr.alt {
background: #f6f6f6;
}
.row1 {
background: #fff;
}
.row2 {
background: #f9f9f9;
}
/* SORTABLE TABLES */
thead th {
padding: 5px 10px;
line-height: normal;
text-transform: uppercase;
background: #f6f6f6;
}
thead th a:link, thead th a:visited {
color: #666;
}
thead th.sorted {
background: #eee;
}
thead th.sorted .text {
padding-right: 42px;
}
table thead th .text span {
padding: 8px 10px;
display: block;
}
table thead th .text a {
display: block;
cursor: pointer;
padding: 8px 10px;
}
table thead th .text a:focus, table thead th .text a:hover {
background: #eee;
}
thead th.sorted a.sortremove {
visibility: hidden;
}
table thead th.sorted:hover a.sortremove {
visibility: visible;
}
table thead th.sorted .sortoptions {
display: block;
padding: 9px 5px 0 5px;
float: right;
text-align: right;
}
table thead th.sorted .sortpriority {
font-size: .8em;
min-width: 12px;
text-align: center;
vertical-align: 3px;
margin-left: 2px;
margin-right: 2px;
}
table thead th.sorted .sortoptions a {
position: relative;
width: 14px;
height: 14px;
display: inline-block;
background: url("../img/sorting-icons.3a097b59f104.svg") 0 0 no-repeat;
background-size: 14px auto;
}
table thead th.sorted .sortoptions a.sortremove {
background-position: 0 0;
}
table thead th.sorted .sortoptions a.sortremove:after {
content: '\\';
position: absolute;
top: -6px;
left: 3px;
font-weight: 200;
font-size: 18px;
color: #999;
}
table thead th.sorted .sortoptions a.sortremove:focus:after,
table thead th.sorted .sortoptions a.sortremove:hover:after {
color: #447e9b;
}
table thead th.sorted .sortoptions a.sortremove:focus,
table thead th.sorted .sortoptions a.sortremove:hover {
background-position: 0 -14px;
}
table thead th.sorted .sortoptions a.ascending {
background-position: 0 -28px;
}
table thead th.sorted .sortoptions a.ascending:focus,
table thead th.sorted .sortoptions a.ascending:hover {
background-position: 0 -42px;
}
table thead th.sorted .sortoptions a.descending {
top: 1px;
background-position: 0 -56px;
}
table thead th.sorted .sortoptions a.descending:focus,
table thead th.sorted .sortoptions a.descending:hover {
background-position: 0 -70px;
}
/* FORM DEFAULTS */
input, textarea, select, .form-row p, form .button {
margin: 2px 0;
padding: 2px 3px;
vertical-align: middle;
font-family: "Roboto", "Lucida Grande", Verdana, Arial, sans-serif;
font-weight: normal;
font-size: 13px;
}
textarea {
vertical-align: top;
}
input[type=text], input[type=password], input[type=email], input[type=url],
input[type=number], textarea, select, .vTextField {
border: 1px solid #ccc;
border-radius: 4px;
padding: 5px 6px;
margin-top: 0;
}
input[type=text]:focus, input[type=password]:focus, input[type=email]:focus,
input[type=url]:focus, input[type=number]:focus, textarea:focus, select:focus,
.vTextField:focus {
border-color: #999;
}
select {
height: 30px;
}
select[multiple] {
min-height: 150px;
}
/* FORM BUTTONS */
.button, input[type=submit], input[type=button], .submit-row input, a.button {
background: #79aec8;
padding: 10px 15px;
border: none;
border-radius: 4px;
color: #fff;
cursor: pointer;
}
a.button {
padding: 4px 5px;
}
.button:active, input[type=submit]:active, input[type=button]:active,
.button:focus, input[type=submit]:focus, input[type=button]:focus,
.button:hover, input[type=submit]:hover, input[type=button]:hover {
background: #609ab6;
}
.button[disabled], input[type=submit][disabled], input[type=button][disabled] {
opacity: 0.4;
}
.button.default, input[type=submit].default, .submit-row input.default {
float: right;
border: none;
font-weight: 400;
background: #417690;
}
.button.default:active, input[type=submit].default:active,
.button.default:focus, input[type=submit].default:focus,
.button.default:hover, input[type=submit].default:hover {
background: #205067;
}
.button[disabled].default,
input[type=submit][disabled].default,
input[type=button][disabled].default {
opacity: 0.4;
}
/* MODULES */
.module {
border: none;
margin-bottom: 30px;
background: #fff;
}
.module p, .module ul, .module h3, .module h4, .module dl, .module pre {
padding-left: 10px;
padding-right: 10px;
}
.module blockquote {
margin-left: 12px;
}
.module ul, .module ol {
margin-left: 1.5em;
}
.module h3 {
margin-top: .6em;
}
.module h2, .module caption, .inline-group h2 {
margin: 0;
padding: 8px;
font-weight: 400;
font-size: 13px;
text-align: left;
background: #79aec8;
color: #fff;
}
.module caption,
.inline-group h2 {
font-size: 12px;
letter-spacing: 0.5px;
text-transform: uppercase;
}
.module table {
border-collapse: collapse;
}
/* MESSAGES & ERRORS */
ul.messagelist {
padding: 0;
margin: 0;
}
ul.messagelist li {
display: block;
font-weight: 400;
font-size: 13px;
padding: 10px 10px 10px 65px;
margin: 0 0 10px 0;
background: #dfd url("../img/icon-yes.d2f9f035226a.svg") 40px 12px no-repeat;
background-size: 16px auto;
color: #333;
}
ul.messagelist li.warning {
background: #ffc url("../img/icon-alert.034cc7d8a67f.svg") 40px 14px no-repeat;
background-size: 14px auto;
}
ul.messagelist li.error {
background: #ffefef url("../img/icon-no.439e821418cd.svg") 40px 12px no-repeat;
background-size: 16px auto;
}
.errornote {
font-size: 14px;
font-weight: 700;
display: block;
padding: 10px 12px;
margin: 0 0 10px 0;
color: #ba2121;
border: 1px solid #ba2121;
border-radius: 4px;
background-color: #fff;
background-position: 5px 12px;
}
ul.errorlist {
margin: 0 0 4px;
padding: 0;
color: #ba2121;
background: #fff;
}
ul.errorlist li {
font-size: 13px;
display: block;
margin-bottom: 4px;
}
ul.errorlist li:first-child {
margin-top: 0;
}
ul.errorlist li a {
color: inherit;
text-decoration: underline;
}
td ul.errorlist {
margin: 0;
padding: 0;
}
td ul.errorlist li {
margin: 0;
}
.form-row.errors {
margin: 0;
border: none;
border-bottom: 1px solid #eee;
background: none;
}
.form-row.errors ul.errorlist li {
padding-left: 0;
}
.errors input, .errors select, .errors textarea {
border: 1px solid #ba2121;
}
div.system-message {
background: #ffc;
margin: 10px;
padding: 6px 8px;
font-size: .8em;
}
div.system-message p.system-message-title {
padding: 4px 5px 4px 25px;
margin: 0;
color: #c11;
background: #ffefef url("../img/icon-no.439e821418cd.svg") 5px 5px no-repeat;
}
.description {
font-size: 12px;
padding: 5px 0 0 12px;
}
/* BREADCRUMBS */
div.breadcrumbs {
background: #79aec8;
padding: 10px 40px;
border: none;
font-size: 14px;
color: #c4dce8;
text-align: left;
}
div.breadcrumbs a {
color: #fff;
}
div.breadcrumbs a:focus, div.breadcrumbs a:hover {
color: #c4dce8;
}
/* ACTION ICONS */
.addlink {
padding-left: 16px;
background: url("../img/icon-addlink.d519b3bab011.svg") 0 1px no-repeat;
}
.changelink, .inlinechangelink {
padding-left: 16px;
background: url("../img/icon-changelink.18d2fd706348.svg") 0 1px no-repeat;
}
.deletelink {
padding-left: 16px;
background: url("../img/icon-deletelink.564ef9dc3854.svg") 0 1px no-repeat;
}
a.deletelink:link, a.deletelink:visited {
color: #CC3434;
}
a.deletelink:focus, a.deletelink:hover {
color: #993333;
text-decoration: none;
}
/* OBJECT TOOLS */
.object-tools {
font-size: 10px;
font-weight: bold;
padding-left: 0;
float: right;
position: relative;
margin-top: -48px;
}
.form-row .object-tools {
margin-top: 5px;
margin-bottom: 5px;
float: none;
height: 2em;
padding-left: 3.5em;
}
.object-tools li {
display: block;
float: left;
margin-left: 5px;
height: 16px;
}
.object-tools a {
border-radius: 15px;
}
.object-tools a:link, .object-tools a:visited {
display: block;
float: left;
padding: 3px 12px;
background: #999;
font-weight: 400;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: #fff;
}
.object-tools a:focus, .object-tools a:hover {
background-color: #417690;
}
.object-tools a:focus{
text-decoration: none;
}
.object-tools a.viewsitelink, .object-tools a.golink,.object-tools a.addlink {
background-repeat: no-repeat;
background-position: 93% center;
padding-right: 26px;
}
.object-tools a.viewsitelink, .object-tools a.golink {
background-image: url("../img/tooltag-arrowright.bbfb788a849e.svg");
}
.object-tools a.addlink {
background-image: url("../img/tooltag-add.e59d620a9742.svg");
}
/* OBJECT HISTORY */
table#change-history {
width: 100%;
}
table#change-history tbody th {
width: 16em;
}
/* PAGE STRUCTURE */
#container {
position: relative;
width: 100%;
min-width: 980px;
padding: 0;
}
#content {
padding: 20px 40px;
}
.dashboard #content {
width: 600px;
}
#content-main {
float: left;
width: 100%;
}
#content-related {
float: right;
width: 260px;
position: relative;
margin-right: -300px;
}
#footer {
clear: both;
padding: 10px;
}
/* COLUMN TYPES */
.colMS {
margin-right: 300px;
}
.colSM {
margin-left: 300px;
}
.colSM #content-related {
float: left;
margin-right: 0;
margin-left: -300px;
}
.colSM #content-main {
float: right;
}
.popup .colM {
width: auto;
}
/* HEADER */
#header {
width: auto;
height: 40px;
padding: 10px 40px;
background: #417690;
line-height: 40px;
color: #ffc;
overflow: hidden;
}
#header a:link, #header a:visited {
color: #fff;
}
#header a:focus , #header a:hover {
text-decoration: underline;
}
#branding {
float: left;
}
#branding h1 {
padding: 0;
margin: 0 20px 0 0;
font-weight: 300;
font-size: 24px;
color: #f5dd5d;
}
#branding h1, #branding h1 a:link, #branding h1 a:visited {
color: #f5dd5d;
}
#branding h2 {
padding: 0 10px;
font-size: 14px;
margin: -8px 0 8px 0;
font-weight: normal;
color: #ffc;
}
#branding a:hover {
text-decoration: none;
}
#user-tools {
float: right;
padding: 0;
margin: 0 0 0 20px;
font-weight: 300;
font-size: 11px;
letter-spacing: 0.5px;
text-transform: uppercase;
text-align: right;
}
#user-tools a {
border-bottom: 1px solid rgba(255, 255, 255, 0.25);
}
#user-tools a:focus, #user-tools a:hover {
text-decoration: none;
border-bottom-color: #79aec8;
color: #79aec8;
}
/* SIDEBAR */
#content-related {
background: #f8f8f8;
}
#content-related .module {
background: none;
}
#content-related h3 {
font-size: 14px;
color: #666;
padding: 0 16px;
margin: 0 0 16px;
}
#content-related h4 {
font-size: 13px;
}
#content-related p {
padding-left: 16px;
padding-right: 16px;
}
#content-related .actionlist {
padding: 0;
margin: 16px;
}
#content-related .actionlist li {
line-height: 1.2;
margin-bottom: 10px;
padding-left: 18px;
}
#content-related .module h2 {
background: none;
padding: 16px;
margin-bottom: 16px;
border-bottom: 1px solid #eaeaea;
font-size: 18px;
color: #333;
}
.delete-confirmation form input[type="submit"] {
background: #ba2121;
border-radius: 4px;
padding: 10px 15px;
color: #fff;
}
.delete-confirmation form input[type="submit"]:active,
.delete-confirmation form input[type="submit"]:focus,
.delete-confirmation form input[type="submit"]:hover {
background: #a41515;
}
.delete-confirmation form .cancel-link {
display: inline-block;
vertical-align: middle;
height: 15px;
line-height: 15px;
background: #ddd;
border-radius: 4px;
padding: 10px 15px;
color: #333;
margin: 0 0 0 10px;
}
.delete-confirmation form .cancel-link:active,
.delete-confirmation form .cancel-link:focus,
.delete-confirmation form .cancel-link:hover {
background: #ccc;
}
/* POPUP */
.popup #content {
padding: 20px;
}
.popup #container {
min-width: 0;
}
.popup #header {
padding: 10px 20px;
}

@ -1,971 +0,0 @@
/*
DJANGO Admin styles
*/
@import url(fonts.css);
body {
margin: 0;
padding: 0;
font-size: 14px;
font-family: "Roboto","Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif;
color: #333;
background: #fff;
}
/* LINKS */
a:link, a:visited {
color: #447e9b;
text-decoration: none;
}
a:focus, a:hover {
color: #036;
}
a:focus {
text-decoration: underline;
}
a img {
border: none;
}
a.section:link, a.section:visited {
color: #fff;
text-decoration: none;
}
a.section:focus, a.section:hover {
text-decoration: underline;
}
/* GLOBAL DEFAULTS */
p, ol, ul, dl {
margin: .2em 0 .8em 0;
}
p {
padding: 0;
line-height: 140%;
}
h1,h2,h3,h4,h5 {
font-weight: bold;
}
h1 {
margin: 0 0 20px;
font-weight: 300;
font-size: 20px;
color: #666;
}
h2 {
font-size: 16px;
margin: 1em 0 .5em 0;
}
h2.subhead {
font-weight: normal;
margin-top: 0;
}
h3 {
font-size: 14px;
margin: .8em 0 .3em 0;
color: #666;
font-weight: bold;
}
h4 {
font-size: 12px;
margin: 1em 0 .8em 0;
padding-bottom: 3px;
}
h5 {
font-size: 10px;
margin: 1.5em 0 .5em 0;
color: #666;
text-transform: uppercase;
letter-spacing: 1px;
}
ul li {
list-style-type: square;
padding: 1px 0;
}
li ul {
margin-bottom: 0;
}
li, dt, dd {
font-size: 13px;
line-height: 20px;
}
dt {
font-weight: bold;
margin-top: 4px;
}
dd {
margin-left: 0;
}
form {
margin: 0;
padding: 0;
}
fieldset {
margin: 0;
padding: 0;
border: none;
border-top: 1px solid #eee;
}
blockquote {
font-size: 11px;
color: #777;
margin-left: 2px;
padding-left: 10px;
border-left: 5px solid #ddd;
}
code, pre {
font-family: "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace;
color: #666;
font-size: 12px;
}
pre.literal-block {
margin: 10px;
background: #eee;
padding: 6px 8px;
}
code strong {
color: #930;
}
hr {
clear: both;
color: #eee;
background-color: #eee;
height: 1px;
border: none;
margin: 0;
padding: 0;
font-size: 1px;
line-height: 1px;
}
/* TEXT STYLES & MODIFIERS */
.small {
font-size: 11px;
}
.tiny {
font-size: 10px;
}
p.tiny {
margin-top: -2px;
}
.mini {
font-size: 10px;
}
p.mini {
margin-top: -3px;
}
.help, p.help, form p.help {
font-size: 11px;
color: #999;
}
.help-tooltip {
cursor: help;
}
p img, h1 img, h2 img, h3 img, h4 img, td img {
vertical-align: middle;
}
.quiet, a.quiet:link, a.quiet:visited {
color: #999;
font-weight: normal;
}
.float-right {
float: right;
}
.float-left {
float: left;
}
.clear {
clear: both;
}
.align-left {
text-align: left;
}
.align-right {
text-align: right;
}
.example {
margin: 10px 0;
padding: 5px 10px;
background: #efefef;
}
.nowrap {
white-space: nowrap;
}
/* TABLES */
table {
border-collapse: collapse;
border-color: #ccc;
}
td, th {
font-size: 13px;
line-height: 16px;
border-bottom: 1px solid #eee;
vertical-align: top;
padding: 8px;
font-family: "Roboto", "Lucida Grande", Verdana, Arial, sans-serif;
}
th {
font-weight: 600;
text-align: left;
}
thead th,
tfoot td {
color: #666;
padding: 5px 10px;
font-size: 11px;
background: #fff;
border: none;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
}
tfoot td {
border-bottom: none;
border-top: 1px solid #eee;
}
thead th.required {
color: #000;
}
tr.alt {
background: #f6f6f6;
}
.row1 {
background: #fff;
}
.row2 {
background: #f9f9f9;
}
/* SORTABLE TABLES */
thead th {
padding: 5px 10px;
line-height: normal;
text-transform: uppercase;
background: #f6f6f6;
}
thead th a:link, thead th a:visited {
color: #666;
}
thead th.sorted {
background: #eee;
}
thead th.sorted .text {
padding-right: 42px;
}
table thead th .text span {
padding: 8px 10px;
display: block;
}
table thead th .text a {
display: block;
cursor: pointer;
padding: 8px 10px;
}
table thead th .text a:focus, table thead th .text a:hover {
background: #eee;
}
thead th.sorted a.sortremove {
visibility: hidden;
}
table thead th.sorted:hover a.sortremove {
visibility: visible;
}
table thead th.sorted .sortoptions {
display: block;
padding: 9px 5px 0 5px;
float: right;
text-align: right;
}
table thead th.sorted .sortpriority {
font-size: .8em;
min-width: 12px;
text-align: center;
vertical-align: 3px;
margin-left: 2px;
margin-right: 2px;
}
table thead th.sorted .sortoptions a {
position: relative;
width: 14px;
height: 14px;
display: inline-block;
background: url(../img/sorting-icons.svg) 0 0 no-repeat;
background-size: 14px auto;
}
table thead th.sorted .sortoptions a.sortremove {
background-position: 0 0;
}
table thead th.sorted .sortoptions a.sortremove:after {
content: '\\';
position: absolute;
top: -6px;
left: 3px;
font-weight: 200;
font-size: 18px;
color: #999;
}
table thead th.sorted .sortoptions a.sortremove:focus:after,
table thead th.sorted .sortoptions a.sortremove:hover:after {
color: #447e9b;
}
table thead th.sorted .sortoptions a.sortremove:focus,
table thead th.sorted .sortoptions a.sortremove:hover {
background-position: 0 -14px;
}
table thead th.sorted .sortoptions a.ascending {
background-position: 0 -28px;
}
table thead th.sorted .sortoptions a.ascending:focus,
table thead th.sorted .sortoptions a.ascending:hover {
background-position: 0 -42px;
}
table thead th.sorted .sortoptions a.descending {
top: 1px;
background-position: 0 -56px;
}
table thead th.sorted .sortoptions a.descending:focus,
table thead th.sorted .sortoptions a.descending:hover {
background-position: 0 -70px;
}
/* FORM DEFAULTS */
input, textarea, select, .form-row p, form .button {
margin: 2px 0;
padding: 2px 3px;
vertical-align: middle;
font-family: "Roboto", "Lucida Grande", Verdana, Arial, sans-serif;
font-weight: normal;
font-size: 13px;
}
textarea {
vertical-align: top;
}
input[type=text], input[type=password], input[type=email], input[type=url],
input[type=number], textarea, select, .vTextField {
border: 1px solid #ccc;
border-radius: 4px;
padding: 5px 6px;
margin-top: 0;
}
input[type=text]:focus, input[type=password]:focus, input[type=email]:focus,
input[type=url]:focus, input[type=number]:focus, textarea:focus, select:focus,
.vTextField:focus {
border-color: #999;
}
select {
height: 30px;
}
select[multiple] {
min-height: 150px;
}
/* FORM BUTTONS */
.button, input[type=submit], input[type=button], .submit-row input, a.button {
background: #79aec8;
padding: 10px 15px;
border: none;
border-radius: 4px;
color: #fff;
cursor: pointer;
}
a.button {
padding: 4px 5px;
}
.button:active, input[type=submit]:active, input[type=button]:active,
.button:focus, input[type=submit]:focus, input[type=button]:focus,
.button:hover, input[type=submit]:hover, input[type=button]:hover {
background: #609ab6;
}
.button[disabled], input[type=submit][disabled], input[type=button][disabled] {
opacity: 0.4;
}
.button.default, input[type=submit].default, .submit-row input.default {
float: right;
border: none;
font-weight: 400;
background: #417690;
}
.button.default:active, input[type=submit].default:active,
.button.default:focus, input[type=submit].default:focus,
.button.default:hover, input[type=submit].default:hover {
background: #205067;
}
.button[disabled].default,
input[type=submit][disabled].default,
input[type=button][disabled].default {
opacity: 0.4;
}
/* MODULES */
.module {
border: none;
margin-bottom: 30px;
background: #fff;
}
.module p, .module ul, .module h3, .module h4, .module dl, .module pre {
padding-left: 10px;
padding-right: 10px;
}
.module blockquote {
margin-left: 12px;
}
.module ul, .module ol {
margin-left: 1.5em;
}
.module h3 {
margin-top: .6em;
}
.module h2, .module caption, .inline-group h2 {
margin: 0;
padding: 8px;
font-weight: 400;
font-size: 13px;
text-align: left;
background: #79aec8;
color: #fff;
}
.module caption,
.inline-group h2 {
font-size: 12px;
letter-spacing: 0.5px;
text-transform: uppercase;
}
.module table {
border-collapse: collapse;
}
/* MESSAGES & ERRORS */
ul.messagelist {
padding: 0;
margin: 0;
}
ul.messagelist li {
display: block;
font-weight: 400;
font-size: 13px;
padding: 10px 10px 10px 65px;
margin: 0 0 10px 0;
background: #dfd url(../img/icon-yes.svg) 40px 12px no-repeat;
background-size: 16px auto;
color: #333;
}
ul.messagelist li.warning {
background: #ffc url(../img/icon-alert.svg) 40px 14px no-repeat;
background-size: 14px auto;
}
ul.messagelist li.error {
background: #ffefef url(../img/icon-no.svg) 40px 12px no-repeat;
background-size: 16px auto;
}
.errornote {
font-size: 14px;
font-weight: 700;
display: block;
padding: 10px 12px;
margin: 0 0 10px 0;
color: #ba2121;
border: 1px solid #ba2121;
border-radius: 4px;
background-color: #fff;
background-position: 5px 12px;
}
ul.errorlist {
margin: 0 0 4px;
padding: 0;
color: #ba2121;
background: #fff;
}
ul.errorlist li {
font-size: 13px;
display: block;
margin-bottom: 4px;
}
ul.errorlist li:first-child {
margin-top: 0;
}
ul.errorlist li a {
color: inherit;
text-decoration: underline;
}
td ul.errorlist {
margin: 0;
padding: 0;
}
td ul.errorlist li {
margin: 0;
}
.form-row.errors {
margin: 0;
border: none;
border-bottom: 1px solid #eee;
background: none;
}
.form-row.errors ul.errorlist li {
padding-left: 0;
}
.errors input, .errors select, .errors textarea {
border: 1px solid #ba2121;
}
div.system-message {
background: #ffc;
margin: 10px;
padding: 6px 8px;
font-size: .8em;
}
div.system-message p.system-message-title {
padding: 4px 5px 4px 25px;
margin: 0;
color: #c11;
background: #ffefef url(../img/icon-no.svg) 5px 5px no-repeat;
}
.description {
font-size: 12px;
padding: 5px 0 0 12px;
}
/* BREADCRUMBS */
div.breadcrumbs {
background: #79aec8;
padding: 10px 40px;
border: none;
font-size: 14px;
color: #c4dce8;
text-align: left;
}
div.breadcrumbs a {
color: #fff;
}
div.breadcrumbs a:focus, div.breadcrumbs a:hover {
color: #c4dce8;
}
/* ACTION ICONS */
.addlink {
padding-left: 16px;
background: url(../img/icon-addlink.svg) 0 1px no-repeat;
}
.changelink, .inlinechangelink {
padding-left: 16px;
background: url(../img/icon-changelink.svg) 0 1px no-repeat;
}
.deletelink {
padding-left: 16px;
background: url(../img/icon-deletelink.svg) 0 1px no-repeat;
}
a.deletelink:link, a.deletelink:visited {
color: #CC3434;
}
a.deletelink:focus, a.deletelink:hover {
color: #993333;
text-decoration: none;
}
/* OBJECT TOOLS */
.object-tools {
font-size: 10px;
font-weight: bold;
padding-left: 0;
float: right;
position: relative;
margin-top: -48px;
}
.form-row .object-tools {
margin-top: 5px;
margin-bottom: 5px;
float: none;
height: 2em;
padding-left: 3.5em;
}
.object-tools li {
display: block;
float: left;
margin-left: 5px;
height: 16px;
}
.object-tools a {
border-radius: 15px;
}
.object-tools a:link, .object-tools a:visited {
display: block;
float: left;
padding: 3px 12px;
background: #999;
font-weight: 400;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: #fff;
}
.object-tools a:focus, .object-tools a:hover {
background-color: #417690;
}
.object-tools a:focus{
text-decoration: none;
}
.object-tools a.viewsitelink, .object-tools a.golink,.object-tools a.addlink {
background-repeat: no-repeat;
background-position: 93% center;
padding-right: 26px;
}
.object-tools a.viewsitelink, .object-tools a.golink {
background-image: url(../img/tooltag-arrowright.svg);
}
.object-tools a.addlink {
background-image: url(../img/tooltag-add.svg);
}
/* OBJECT HISTORY */
table#change-history {
width: 100%;
}
table#change-history tbody th {
width: 16em;
}
/* PAGE STRUCTURE */
#container {
position: relative;
width: 100%;
min-width: 980px;
padding: 0;
}
#content {
padding: 20px 40px;
}
.dashboard #content {
width: 600px;
}
#content-main {
float: left;
width: 100%;
}
#content-related {
float: right;
width: 260px;
position: relative;
margin-right: -300px;
}
#footer {
clear: both;
padding: 10px;
}
/* COLUMN TYPES */
.colMS {
margin-right: 300px;
}
.colSM {
margin-left: 300px;
}
.colSM #content-related {
float: left;
margin-right: 0;
margin-left: -300px;
}
.colSM #content-main {
float: right;
}
.popup .colM {
width: auto;
}
/* HEADER */
#header {
width: auto;
height: 40px;
padding: 10px 40px;
background: #417690;
line-height: 40px;
color: #ffc;
overflow: hidden;
}
#header a:link, #header a:visited {
color: #fff;
}
#header a:focus , #header a:hover {
text-decoration: underline;
}
#branding {
float: left;
}
#branding h1 {
padding: 0;
margin: 0 20px 0 0;
font-weight: 300;
font-size: 24px;
color: #f5dd5d;
}
#branding h1, #branding h1 a:link, #branding h1 a:visited {
color: #f5dd5d;
}
#branding h2 {
padding: 0 10px;
font-size: 14px;
margin: -8px 0 8px 0;
font-weight: normal;
color: #ffc;
}
#branding a:hover {
text-decoration: none;
}
#user-tools {
float: right;
padding: 0;
margin: 0 0 0 20px;
font-weight: 300;
font-size: 11px;
letter-spacing: 0.5px;
text-transform: uppercase;
text-align: right;
}
#user-tools a {
border-bottom: 1px solid rgba(255, 255, 255, 0.25);
}
#user-tools a:focus, #user-tools a:hover {
text-decoration: none;
border-bottom-color: #79aec8;
color: #79aec8;
}
/* SIDEBAR */
#content-related {
background: #f8f8f8;
}
#content-related .module {
background: none;
}
#content-related h3 {
font-size: 14px;
color: #666;
padding: 0 16px;
margin: 0 0 16px;
}
#content-related h4 {
font-size: 13px;
}
#content-related p {
padding-left: 16px;
padding-right: 16px;
}
#content-related .actionlist {
padding: 0;
margin: 16px;
}
#content-related .actionlist li {
line-height: 1.2;
margin-bottom: 10px;
padding-left: 18px;
}
#content-related .module h2 {
background: none;
padding: 16px;
margin-bottom: 16px;
border-bottom: 1px solid #eaeaea;
font-size: 18px;
color: #333;
}
.delete-confirmation form input[type="submit"] {
background: #ba2121;
border-radius: 4px;
padding: 10px 15px;
color: #fff;
}
.delete-confirmation form input[type="submit"]:active,
.delete-confirmation form input[type="submit"]:focus,
.delete-confirmation form input[type="submit"]:hover {
background: #a41515;
}
.delete-confirmation form .cancel-link {
display: inline-block;
vertical-align: middle;
height: 15px;
line-height: 15px;
background: #ddd;
border-radius: 4px;
padding: 10px 15px;
color: #333;
margin: 0 0 0 10px;
}
.delete-confirmation form .cancel-link:active,
.delete-confirmation form .cancel-link:focus,
.delete-confirmation form .cancel-link:hover {
background: #ccc;
}
/* POPUP */
.popup #content {
padding: 20px;
}
.popup #container {
min-width: 0;
}
.popup #header {
padding: 10px 20px;
}

Binary file not shown.

@ -1,342 +0,0 @@
/* CHANGELISTS */
#changelist {
position: relative;
width: 100%;
}
#changelist table {
width: 100%;
}
.change-list .hiddenfields { display:none; }
.change-list .filtered table {
border-right: none;
}
.change-list .filtered {
min-height: 400px;
}
.change-list .filtered .results, .change-list .filtered .paginator,
.filtered #toolbar, .filtered div.xfull {
margin-right: 280px;
width: auto;
}
.change-list .filtered table tbody th {
padding-right: 1em;
}
#changelist-form .results {
overflow-x: auto;
}
#changelist .toplinks {
border-bottom: 1px solid #ddd;
}
#changelist .paginator {
color: #666;
border-bottom: 1px solid #eee;
background: #fff;
overflow: hidden;
}
/* CHANGELIST TABLES */
#changelist table thead th {
padding: 0;
white-space: nowrap;
vertical-align: middle;
}
#changelist table thead th.action-checkbox-column {
width: 1.5em;
text-align: center;
}
#changelist table tbody td.action-checkbox {
text-align: center;
}
#changelist table tfoot {
color: #666;
}
/* TOOLBAR */
#changelist #toolbar {
padding: 8px 10px;
margin-bottom: 15px;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
background: #f8f8f8;
color: #666;
}
#changelist #toolbar form input {
border-radius: 4px;
font-size: 14px;
padding: 5px;
color: #333;
}
#changelist #toolbar form #searchbar {
height: 19px;
border: 1px solid #ccc;
padding: 2px 5px;
margin: 0;
vertical-align: top;
font-size: 13px;
}
#changelist #toolbar form #searchbar:focus {
border-color: #999;
}
#changelist #toolbar form input[type="submit"] {
border: 1px solid #ccc;
padding: 2px 10px;
margin: 0;
vertical-align: middle;
background: #fff;
box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
cursor: pointer;
color: #333;
}
#changelist #toolbar form input[type="submit"]:focus,
#changelist #toolbar form input[type="submit"]:hover {
border-color: #999;
}
#changelist #changelist-search img {
vertical-align: middle;
margin-right: 4px;
}
/* FILTER COLUMN */
#changelist-filter {
position: absolute;
top: 0;
right: 0;
z-index: 1000;
width: 240px;
background: #f8f8f8;
border-left: none;
margin: 0;
}
#changelist-filter h2 {
font-size: 14px;
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 5px 15px;
margin-bottom: 12px;
border-bottom: none;
}
#changelist-filter h3 {
font-weight: 400;
font-size: 14px;
padding: 0 15px;
margin-bottom: 10px;
}
#changelist-filter ul {
margin: 5px 0;
padding: 0 15px 15px;
border-bottom: 1px solid #eaeaea;
}
#changelist-filter ul:last-child {
border-bottom: none;
padding-bottom: none;
}
#changelist-filter li {
list-style-type: none;
margin-left: 0;
padding-left: 0;
}
#changelist-filter a {
display: block;
color: #999;
}
#changelist-filter li.selected {
border-left: 5px solid #eaeaea;
padding-left: 10px;
margin-left: -15px;
}
#changelist-filter li.selected a {
color: #5b80b2;
}
#changelist-filter a:focus, #changelist-filter a:hover,
#changelist-filter li.selected a:focus,
#changelist-filter li.selected a:hover {
color: #036;
}
/* DATE DRILLDOWN */
.change-list ul.toplinks {
display: block;
float: left;
padding: 0;
margin: 0;
width: 100%;
}
.change-list ul.toplinks li {
padding: 3px 6px;
font-weight: bold;
list-style-type: none;
display: inline-block;
}
.change-list ul.toplinks .date-back a {
color: #999;
}
.change-list ul.toplinks .date-back a:focus,
.change-list ul.toplinks .date-back a:hover {
color: #036;
}
/* PAGINATOR */
.paginator {
font-size: 13px;
padding-top: 10px;
padding-bottom: 10px;
line-height: 22px;
margin: 0;
border-top: 1px solid #ddd;
}
.paginator a:link, .paginator a:visited {
padding: 2px 6px;
background: #79aec8;
text-decoration: none;
color: #fff;
}
.paginator a.showall {
padding: 0;
border: none;
background: none;
color: #5b80b2;
}
.paginator a.showall:focus, .paginator a.showall:hover {
background: none;
color: #036;
}
.paginator .end {
margin-right: 6px;
}
.paginator .this-page {
padding: 2px 6px;
font-weight: bold;
font-size: 13px;
vertical-align: top;
}
.paginator a:focus, .paginator a:hover {
color: white;
background: #036;
}
/* ACTIONS */
.filtered .actions {
margin-right: 280px;
border-right: none;
}
#changelist table input {
margin: 0;
vertical-align: baseline;
}
#changelist table tbody tr.selected {
background-color: #FFFFCC;
}
#changelist .actions {
padding: 10px;
background: #fff;
border-top: none;
border-bottom: none;
line-height: 24px;
color: #999;
}
#changelist .actions.selected {
background: #fffccf;
border-top: 1px solid #fffee8;
border-bottom: 1px solid #edecd6;
}
#changelist .actions span.all,
#changelist .actions span.action-counter,
#changelist .actions span.clear,
#changelist .actions span.question {
font-size: 13px;
margin: 0 0.5em;
display: none;
}
#changelist .actions:last-child {
border-bottom: none;
}
#changelist .actions select {
vertical-align: top;
height: 24px;
background: none;
color: #000;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
padding: 0 0 0 4px;
margin: 0;
margin-left: 10px;
}
#changelist .actions select:focus {
border-color: #999;
}
#changelist .actions label {
display: inline-block;
vertical-align: middle;
font-size: 13px;
}
#changelist .actions .button {
font-size: 13px;
border: 1px solid #ccc;
border-radius: 4px;
background: #fff;
box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
cursor: pointer;
height: 24px;
line-height: 1;
padding: 4px 8px;
margin: 0;
color: #333;
}
#changelist .actions .button:focus, #changelist .actions .button:hover {
border-color: #999;
}

@ -1,342 +0,0 @@
/* CHANGELISTS */
#changelist {
position: relative;
width: 100%;
}
#changelist table {
width: 100%;
}
.change-list .hiddenfields { display:none; }
.change-list .filtered table {
border-right: none;
}
.change-list .filtered {
min-height: 400px;
}
.change-list .filtered .results, .change-list .filtered .paginator,
.filtered #toolbar, .filtered div.xfull {
margin-right: 280px;
width: auto;
}
.change-list .filtered table tbody th {
padding-right: 1em;
}
#changelist-form .results {
overflow-x: auto;
}
#changelist .toplinks {
border-bottom: 1px solid #ddd;
}
#changelist .paginator {
color: #666;
border-bottom: 1px solid #eee;
background: #fff;
overflow: hidden;
}
/* CHANGELIST TABLES */
#changelist table thead th {
padding: 0;
white-space: nowrap;
vertical-align: middle;
}
#changelist table thead th.action-checkbox-column {
width: 1.5em;
text-align: center;
}
#changelist table tbody td.action-checkbox {
text-align: center;
}
#changelist table tfoot {
color: #666;
}
/* TOOLBAR */
#changelist #toolbar {
padding: 8px 10px;
margin-bottom: 15px;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
background: #f8f8f8;
color: #666;
}
#changelist #toolbar form input {
border-radius: 4px;
font-size: 14px;
padding: 5px;
color: #333;
}
#changelist #toolbar form #searchbar {
height: 19px;
border: 1px solid #ccc;
padding: 2px 5px;
margin: 0;
vertical-align: top;
font-size: 13px;
}
#changelist #toolbar form #searchbar:focus {
border-color: #999;
}
#changelist #toolbar form input[type="submit"] {
border: 1px solid #ccc;
padding: 2px 10px;
margin: 0;
vertical-align: middle;
background: #fff;
box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
cursor: pointer;
color: #333;
}
#changelist #toolbar form input[type="submit"]:focus,
#changelist #toolbar form input[type="submit"]:hover {
border-color: #999;
}
#changelist #changelist-search img {
vertical-align: middle;
margin-right: 4px;
}
/* FILTER COLUMN */
#changelist-filter {
position: absolute;
top: 0;
right: 0;
z-index: 1000;
width: 240px;
background: #f8f8f8;
border-left: none;
margin: 0;
}
#changelist-filter h2 {
font-size: 14px;
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 5px 15px;
margin-bottom: 12px;
border-bottom: none;
}
#changelist-filter h3 {
font-weight: 400;
font-size: 14px;
padding: 0 15px;
margin-bottom: 10px;
}
#changelist-filter ul {
margin: 5px 0;
padding: 0 15px 15px;
border-bottom: 1px solid #eaeaea;
}
#changelist-filter ul:last-child {
border-bottom: none;
padding-bottom: none;
}
#changelist-filter li {
list-style-type: none;
margin-left: 0;
padding-left: 0;
}
#changelist-filter a {
display: block;
color: #999;
}
#changelist-filter li.selected {
border-left: 5px solid #eaeaea;
padding-left: 10px;
margin-left: -15px;
}
#changelist-filter li.selected a {
color: #5b80b2;
}
#changelist-filter a:focus, #changelist-filter a:hover,
#changelist-filter li.selected a:focus,
#changelist-filter li.selected a:hover {
color: #036;
}
/* DATE DRILLDOWN */
.change-list ul.toplinks {
display: block;
float: left;
padding: 0;
margin: 0;
width: 100%;
}
.change-list ul.toplinks li {
padding: 3px 6px;
font-weight: bold;
list-style-type: none;
display: inline-block;
}
.change-list ul.toplinks .date-back a {
color: #999;
}
.change-list ul.toplinks .date-back a:focus,
.change-list ul.toplinks .date-back a:hover {
color: #036;
}
/* PAGINATOR */
.paginator {
font-size: 13px;
padding-top: 10px;
padding-bottom: 10px;
line-height: 22px;
margin: 0;
border-top: 1px solid #ddd;
}
.paginator a:link, .paginator a:visited {
padding: 2px 6px;
background: #79aec8;
text-decoration: none;
color: #fff;
}
.paginator a.showall {
padding: 0;
border: none;
background: none;
color: #5b80b2;
}
.paginator a.showall:focus, .paginator a.showall:hover {
background: none;
color: #036;
}
.paginator .end {
margin-right: 6px;
}
.paginator .this-page {
padding: 2px 6px;
font-weight: bold;
font-size: 13px;
vertical-align: top;
}
.paginator a:focus, .paginator a:hover {
color: white;
background: #036;
}
/* ACTIONS */
.filtered .actions {
margin-right: 280px;
border-right: none;
}
#changelist table input {
margin: 0;
vertical-align: baseline;
}
#changelist table tbody tr.selected {
background-color: #FFFFCC;
}
#changelist .actions {
padding: 10px;
background: #fff;
border-top: none;
border-bottom: none;
line-height: 24px;
color: #999;
}
#changelist .actions.selected {
background: #fffccf;
border-top: 1px solid #fffee8;
border-bottom: 1px solid #edecd6;
}
#changelist .actions span.all,
#changelist .actions span.action-counter,
#changelist .actions span.clear,
#changelist .actions span.question {
font-size: 13px;
margin: 0 0.5em;
display: none;
}
#changelist .actions:last-child {
border-bottom: none;
}
#changelist .actions select {
vertical-align: top;
height: 24px;
background: none;
color: #000;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
padding: 0 0 0 4px;
margin: 0;
margin-left: 10px;
}
#changelist .actions select:focus {
border-color: #999;
}
#changelist .actions label {
display: inline-block;
vertical-align: middle;
font-size: 13px;
}
#changelist .actions .button {
font-size: 13px;
border: 1px solid #ccc;
border-radius: 4px;
background: #fff;
box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
cursor: pointer;
height: 24px;
line-height: 1;
padding: 4px 8px;
margin: 0;
color: #333;
}
#changelist .actions .button:focus, #changelist .actions .button:hover {
border-color: #999;
}

@ -1,30 +0,0 @@
/* DASHBOARD */
.dashboard .module table th {
width: 100%;
}
.dashboard .module table td {
white-space: nowrap;
}
.dashboard .module table td a {
display: block;
padding-right: .6em;
}
/* RECENT ACTIONS MODULE */
.module ul.actionlist {
margin-left: 0;
}
ul.actionlist li {
list-style-type: none;
}
ul.actionlist li {
overflow: hidden;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
}

@ -1,30 +0,0 @@
/* DASHBOARD */
.dashboard .module table th {
width: 100%;
}
.dashboard .module table td {
white-space: nowrap;
}
.dashboard .module table td a {
display: block;
padding-right: .6em;
}
/* RECENT ACTIONS MODULE */
.module ul.actionlist {
margin-left: 0;
}
ul.actionlist li {
list-style-type: none;
}
ul.actionlist li {
overflow: hidden;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
}

Binary file not shown.

@ -1,20 +0,0 @@
@font-face {
font-family: 'Roboto';
src: url("../fonts/Roboto-Bold-webfont.2ad99072841e.woff");
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Roboto';
src: url("../fonts/Roboto-Regular-webfont.ec39515ae8c6.woff");
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Roboto';
src: url("../fonts/Roboto-Light-webfont.b446c2399bb6.woff");
font-weight: 300;
font-style: normal;
}

@ -1,20 +0,0 @@
@font-face {
font-family: 'Roboto';
src: url('../fonts/Roboto-Bold-webfont.woff');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Roboto';
src: url('../fonts/Roboto-Regular-webfont.woff');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Roboto';
src: url('../fonts/Roboto-Light-webfont.woff');
font-weight: 300;
font-style: normal;
}

Binary file not shown.

@ -1 +0,0 @@
/* Empty CSS from Django Suit app to override original file */

@ -1 +0,0 @@
/* Empty CSS from Django Suit app to override original file */

@ -1,78 +0,0 @@
/* LOGIN FORM */
body.login {
background: #f8f8f8;
}
.login #header {
height: auto;
padding: 5px 16px;
}
.login #header h1 {
font-size: 18px;
}
.login #header h1 a {
color: #fff;
}
.login #content {
padding: 20px 20px 0;
}
.login #container {
background: #fff;
border: 1px solid #eaeaea;
border-radius: 4px;
overflow: hidden;
width: 28em;
min-width: 300px;
margin: 100px auto;
}
.login #content-main {
width: 100%;
}
.login .form-row {
padding: 4px 0;
float: left;
width: 100%;
border-bottom: none;
}
.login .form-row label {
padding-right: 0.5em;
line-height: 2em;
font-size: 1em;
clear: both;
color: #333;
}
.login .form-row #id_username, .login .form-row #id_password {
clear: both;
padding: 8px;
width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.login span.help {
font-size: 10px;
display: block;
}
.login .submit-row {
clear: both;
padding: 1em 0 0 9.4em;
margin: 0;
border: none;
background: none;
text-align: left;
}
.login .password-reset-link {
text-align: center;
}

@ -1,78 +0,0 @@
/* LOGIN FORM */
body.login {
background: #f8f8f8;
}
.login #header {
height: auto;
padding: 5px 16px;
}
.login #header h1 {
font-size: 18px;
}
.login #header h1 a {
color: #fff;
}
.login #content {
padding: 20px 20px 0;
}
.login #container {
background: #fff;
border: 1px solid #eaeaea;
border-radius: 4px;
overflow: hidden;
width: 28em;
min-width: 300px;
margin: 100px auto;
}
.login #content-main {
width: 100%;
}
.login .form-row {
padding: 4px 0;
float: left;
width: 100%;
border-bottom: none;
}
.login .form-row label {
padding-right: 0.5em;
line-height: 2em;
font-size: 1em;
clear: both;
color: #333;
}
.login .form-row #id_username, .login .form-row #id_password {
clear: both;
padding: 8px;
width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.login span.help {
font-size: 10px;
display: block;
}
.login .submit-row {
clear: both;
padding: 1em 0 0 9.4em;
margin: 0;
border: none;
background: none;
text-align: left;
}
.login .password-reset-link {
text-align: center;
}

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save