├── .gitignore ├── README.md ├── ecommerce ├── accounts │ ├── __init__.py │ ├── admin.py │ ├── forms.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── signals.py │ ├── tests.py │ └── views.py ├── carts │ ├── __init__.py │ ├── admin.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── db.sqlite3 ├── ecommerce │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── main ├── manage.py ├── marketing │ ├── __init__.py │ ├── admin.py │ ├── forms.py │ ├── middleware.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── orders │ ├── __init__.py │ ├── admin.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ ├── utils.py │ └── views.py ├── products │ ├── __init__.py │ ├── admin.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py └── templates │ ├── accounts │ ├── activation_complete.html │ └── activation_message.txt │ ├── base.html │ ├── cart │ └── view.html │ ├── form.html │ ├── modal.html │ ├── navbar.html │ ├── orders │ ├── checkout.html │ └── user.html │ └── products │ ├── all.html │ ├── home.html │ ├── results.html │ └── single.html ├── requirements.txt └── static ├── media ├── images │ └── marketing │ │ └── slider │ │ ├── Screen Shot 2014-10-30 at 1.32.55 PM.png │ │ ├── Screen Shot 2014-10-30 at 1.32.55 PM_1.png │ │ ├── seven-mile-beach-grand-cayman-1.jpg │ │ └── seven-mile-beach-grand-cayman-1_1.jpg └── products │ └── images │ ├── 4001_102.jpg │ ├── Screen_Shot_2014-07-03_at_12.57.47_PM.png │ ├── Screen_Shot_2014-07-03_at_12.57.47_PM_1.png │ ├── Screen_Shot_2014-07-03_at_12.57.47_PM_2.png │ ├── T-Shirt.jpg │ ├── placeholder.jpg │ ├── placeholder_1.jpg │ └── search.jpg ├── static_files ├── css │ ├── bootstrap-theme.min.css │ └── bootstrap.min.css └── img │ └── placeholder.svg └── static_root ├── admin ├── css │ ├── base.css │ ├── changelists.css │ ├── dashboard.css │ ├── forms.css │ ├── ie.css │ ├── login.css │ ├── rtl.css │ └── widgets.css ├── img │ ├── changelist-bg.gif │ ├── changelist-bg_rtl.gif │ ├── chooser-bg.gif │ ├── chooser_stacked-bg.gif │ ├── default-bg-reverse.gif │ ├── default-bg.gif │ ├── deleted-overlay.gif │ ├── gis │ │ ├── move_vertex_off.png │ │ └── move_vertex_on.png │ ├── icon-no.gif │ ├── icon-unknown.gif │ ├── icon-yes.gif │ ├── icon_addlink.gif │ ├── icon_alert.gif │ ├── icon_calendar.gif │ ├── icon_changelink.gif │ ├── icon_clock.gif │ ├── icon_deletelink.gif │ ├── icon_error.gif │ ├── icon_searchbox.png │ ├── icon_success.gif │ ├── inline-delete-8bit.png │ ├── inline-delete.png │ ├── inline-restore-8bit.png │ ├── inline-restore.png │ ├── inline-splitter-bg.gif │ ├── nav-bg-grabber.gif │ ├── nav-bg-reverse.gif │ ├── nav-bg-selected.gif │ ├── nav-bg.gif │ ├── selector-icons.gif │ ├── selector-search.gif │ ├── sorting-icons.gif │ ├── tool-left.gif │ ├── tool-left_over.gif │ ├── tool-right.gif │ ├── tool-right_over.gif │ ├── tooltag-add.gif │ ├── tooltag-add_over.gif │ ├── tooltag-arrowright.gif │ └── tooltag-arrowright_over.gif └── js │ ├── LICENSE-JQUERY.txt │ ├── SelectBox.js │ ├── SelectFilter2.js │ ├── actions.js │ ├── actions.min.js │ ├── admin │ ├── DateTimeShortcuts.js │ └── RelatedObjectLookups.js │ ├── calendar.js │ ├── collapse.js │ ├── collapse.min.js │ ├── core.js │ ├── inlines.js │ ├── inlines.min.js │ ├── jquery.init.js │ ├── jquery.js │ ├── jquery.min.js │ ├── prepopulate.js │ ├── prepopulate.min.js │ ├── timeparse.js │ └── urlify.js ├── css ├── bootstrap-theme.min.css └── bootstrap.min.css └── img └── placeholder.svg /.gitignore: -------------------------------------------------------------------------------- 1 | ecommerce/ecommerce/email_settings.py 2 | 3 | #ignore sublime project 4 | *.sublime-project 5 | *.sublime-workspace 6 | 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | 11 | 12 | .DS_Store 13 | 14 | # C extensions 15 | *.so 16 | 17 | # Distribution / packaging 18 | .Python 19 | env/ 20 | bin/ 21 | include/ 22 | build/ 23 | develop-eggs/ 24 | dist/ 25 | eggs/ 26 | lib/ 27 | lib64/ 28 | parts/ 29 | sdist/ 30 | var/ 31 | *.egg-info/ 32 | .installed.cfg 33 | *.egg 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .coverage 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | 60 | # Sphinx documentation 61 | docs/_build/ 62 | 63 | # PyBuilder 64 | target/ 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | eCommerce Project 2 | ========= 3 | 4 | Coding for Entrepreneurs Tutorial for building an eCommerce web application with Django ([djangoproject.com](http://djangoproject.com)) & Bootstrap ([getbootstrap.com](http:getbootstrap.com)). Topics include Python, HTML, CSS, JQuery, AJAX, and more. 5 | 6 | Learn step-by-step to launch an eCommerce web app using [Django](http://djangoproject.com) for backend, [Bootstrap](http:getbootstrap.com) for front-end, [Heroku](http://heroku.com) for hosting, and [Stripe](http://stripe.com) for payment proccessing. 7 | 8 | Live, working eCommerce store: _Coming Soon_ 9 | 10 | Each Lecture Name is linked to the related source code. 11 | 12 | ## Lecture Code 13 | [Product App Views & Templates](../../tree/6e71fc06e0dfc3acac80269a0e1c2ba3e537ef15) 14 | 15 | [Context in Templates](../../tree/28a1325278f24b491938878b5001f820f31a51eb) 16 | 17 | [Add Bootstrap](../../tree/15d8d3ae7f0c63887f0247a1a69b8cbd25fd794d) 18 | 19 | [Using Blocks](../../tree/8d4fea9dbc34518f9762877d2821bc7656703d93) 20 | 21 | [First Model](../../tree/12a1f2f20830dedb1557fca15d8de6b744fb3d79) 22 | 23 | [The Python Shell](../../tree/7938b3fa021ccd866ccfd14d235a2acbf3389d03) 24 | 25 | [Customize Admin](../../tree/7a8e74f216a6850f057b5702fc10052136da8c0e) 26 | 27 | [Querysets & South](../../tree/80eb9b523457d4e930d0f8848a95f4120612f34f) 28 | 29 | [Add Images](../../tree/791f7e007834ac1cd829dbbf5616cdc4fc06f0bb) 30 | 31 | [Static Files](../../tree/99850cb3163e67f8eaa2976496eed81f81d47332) 32 | 33 | [Products on the Homepage](../../tree/7a38da72b828fadebf29e70c3c2f42b5f43a3af3) 34 | 35 | [Unique Slugs for Products](../../tree/ef18c479654772105236aa2e2b3f2ff0d5fe9a47) 36 | 37 | [URL Patterns for Slugs](../../tree/700574cdec346e1d30860caecff661a691672ffd) 38 | 39 | [Get Absolute URL](../../tree/d58946938216a5319b2a6a34da608d41314a0f4b) 40 | 41 | [Boostrap Images Part 1](../../tree/d38321a6f60f2926e922f33c6990e23271) 42 | 43 | [Product Search](../../tree/b6abc68519a540ea94a01de0775a86c862) 44 | 45 | [Product Page Part 1](../../tree/5ed294a1c33875e8e4d10d5d927f5617b4) 46 | 47 | [Cart App](../../tree/545ee70309ed9d0cd68f597d67aa82a3af3bfb70) 48 | 49 | [Update Cart](../../tree/580a2d1d7fb1318291e809eda02a846ef80c6811) 50 | 51 | [Django Sessions](../../tree/ad2929272805a7ea26bc903d0051a61b57b4c78c) 52 | 53 | [Cart Count in Navbar](../../tree/0871548d6b8693a34a63ee711e6591445dd597) 54 | 55 | [Cart Item](../../tree/2c0567fa1b6d100755e7709985a81a9e9da100) 56 | 57 | [Unique Cart Items](../../tree/727e57d0aef6aea6fed08faec3c5318d4e8aec) 58 | 59 | [Qty & Attr](../../tree/95481f2f1cfe29e1ee40fed88d1cab0c6daf51) 60 | 61 | [Cart Item Notes](../../tree/d012f248e2128de2327767f3e0ffa9753506ec) 62 | 63 | [Product Variations](../../tree/f84c2cea4b96d8515895dcb16e44433c1d1e99) 64 | 65 | [Product Variations pt 2](../../tree/cb56edd19823d0bba6adbedd8ff0c1b7c57c73) 66 | 67 | [Product Variations pt 3](../../tree/886f394f7b2d9b8e6612507f89191a4b072864) 68 | 69 | [Remove From Cart](../../tree/e8a2c81a7d370a5c6e89889bd96dc4dbda31dd) 70 | 71 | [Update Search & Home](../../tree/145ffba13ec59762c9222156e9fcf6f17d) 72 | 73 | [Orders App](../../tree/5b758fc7f2c51e9695d65815ddce51f306274f11) 74 | 75 | [Checkout Pt 1](../../tree/e9602e8e76d6eac1dd5c1b286f3615ced3d264b8) 76 | 77 | [All Products Page](../../tree/3febe9dff97578d442fd70cfb62c27427996b5cc) 78 | 79 | [Update Order Model](../../tree/31f9ebf2dcb7e8653c1a3bf058b1a89003522c9a) 80 | 81 | [Login Required Checkout](../../tree/4d5dacbc43ea4867fb45a88d4c7b22c6c39ff42c) 82 | 83 | [User Stripe Account](../../tree/b30b42131fb7d56c5d30b948ecec46ca82db56e) 84 | 85 | [Logout View & Login View](../../tree/e896d74dc94a38b0b5b3bfad9abe3c698eddf04d) 86 | 87 | [Form Validation](../../tree/cab6168fcbd9812c3d378175e4d8215757081337) 88 | 89 | [Registration Form](../../tree/51f5b88f6d10bf921e46c1311d178bcbab45f275) 90 | 91 | [Unique Email & Post Save Signal](../../tree/2caa0ad81a5089cbd22af9a612f9fd0349eb80c1) 92 | 93 | [Email Confirmed & Activation Method](../../tree/b5da41f513b3cfc72080023b41345f664d661833) 94 | 95 | [Setup Email Ability](../../tree/35855c4bc69e6e25092645037d64bb167250936f) 96 | 97 | [Activation Hash & View](../../tree/9e1c40b563b341010ceb86a5230da74047c29661) 98 | 99 | [Redirects](../../tree/36bdcf58cafe3a190d6897b19bf3ab7c620ef20e) 100 | 101 | [Bootstrap Alert Messages](../../tree/14842653ca5b01e437f85ac2ff215e1d3a95837a) 102 | 103 | [Django Messages](../../tree/516340117137a76e2c790cc358cbd1cda9fd4e01) 104 | 105 | [Marketing App](../../tree/67a303e0408d3f9ded3c555e77f1a16ac57ef2a0) 106 | 107 | [Middleware for Marketing Message](../../tree/471163df2dc9d610fa9a50474e906f76ba3a36cb) 108 | 109 | [Custom Model Queryset and Model Managers](../../tree/38e1bd4d36eea3ba6ad39da766d6fc9df6c896aa) 110 | 111 | [Message Display Duration](../../tree/6fb9cb371993c3e67369bd81ab0d7bbc507aa9bb) 112 | 113 | [Ajax & Timezone](../../tree/c7c6dfbd639db6c89b18d350525a5e395fa02237) 114 | 115 | [Boostrap Carousel](../../tree/e0283864dfc082b732e37252b295262ca26449a8) 116 | 117 | [Marketing Slider](../../tree/92fb7f14eb42f5e3564ba7932f9bb1a5182601ac) 118 | 119 | [Featured Sliders & Slider Links](../../tree/aa936255ced66c4f2bd2521e7854c3efca578169) 120 | 121 | [Bootstrap Modal](../../tree/d67ab1dc0f9c0d895da188e5df35a3358db2fc25) 122 | 123 | [Add Form to Modal](../../tree/bacb0d98d567dbddcd2cc68aba9bf756fa74d1fe) 124 | 125 | [Ajax Email Sign Up](../../tree/f71fbed116cc14dfbbd83d4cac224a078d66f9c6) 126 | 127 | [Store Email Marketing Sign Ups](../../tree/04d6ce26264a49ea33d1eef27fff86892582c2fb) 128 | 129 | [Checkout: Address Model](../../tree/74941650062c85b427ac6778af92d3fa0e9b0bc6) 130 | 131 | [Checkout: Address View](../../tree/56121862b7a97aee99627de986d84c75f03d85bf) 132 | 133 | [Checkout: Selecting Addresses](../../tree/6fe25974125a9977ee5e37a154a9b7495f263512) 134 | 135 | [Checkout - User Default Address](../../tree/6d749f2f373342ed3640fa8daf75c8d7efd71d67) 136 | 137 | [Stripe Payment Form](../../tree/2d1f98c211a0e960810d18a0d2f5bb2b14141bd6) 138 | 139 | [Stripe Charge](../../tree/ebac347724bd7a0b605d79dbcec408e588a6948d) 140 | 141 | [Order Total](../../tree/36e79134cbc674662d18cdbdbddf930bae293ddd) 142 | 143 | [Minor Refactoring for User Model](../../tree/d44f85e40d371c6cf4322145a70000cf5023e603) 144 | 145 | [Calculating Accurate Order Total](../../tree/090cd17fd656ed8af0dc79a72d252a978978b263) 146 | 147 | [Add Billing and Shipping to Stripe & Order](../../tree/358bb4f8df5f1d7bef9a02d8994dbf427bd641ac) 148 | 149 | [Dynamic Address](../../tree/7bcb3171a63acb45279ccdba6310ba56d0aaafc3) 150 | 151 | [Product Categories](../../tree/e7b10d38f8c546a739246aa10b71f3c421adc9f5) 152 | 153 | [Update Variation Defaults](../../tree/9bd8d9eff3ead23b2635f7ed033bb6a3594be22a) 154 | 155 | [Convert project from Django 1.6 to 1.7](../../tree/08f96a9c4cac09d45002d609bf7124b70547d2c0) 156 | 157 | [Checkout - Improve UI part 1](../../tree/2d06bca6fb3b40672c1e6a81d3392a019c08e2d9) 158 | 159 | [Checkout - Improve UI part 2](../../tree/33127f9e78a09f6e1d29a62e2682ded71fea95b1) 160 | 161 | 162 | 163 | ## Installation & Troubleshooting Guides 164 | [View All](../../../Guides/) 165 | 166 | [Install Pillow for Django ImageField](https://github.com/codingforentrepreneurs/Guides/blob/master/imagefield_and_pillow.md) 167 | 168 | [Reactivate Ecommerce Virtualenv](https://github.com/codingforentrepreneurs/Guides/blob/master/reactivate_virtualenv.md) 169 | -------------------------------------------------------------------------------- /ecommerce/accounts/__init__.py: -------------------------------------------------------------------------------- 1 | import signals -------------------------------------------------------------------------------- /ecommerce/accounts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | from .models import UserStripe, EmailConfirmed, EmailMarketingSignUp, UserAddress, UserDefaultAddress 5 | 6 | 7 | class UserAddressAdmin(admin.ModelAdmin): 8 | class Meta: 9 | model = UserAddress 10 | 11 | admin.site.register(UserAddress, UserAddressAdmin) 12 | 13 | 14 | admin.site.register(UserDefaultAddress) 15 | 16 | 17 | admin.site.register(UserStripe) 18 | 19 | admin.site.register(EmailConfirmed) 20 | 21 | 22 | class EmailMarketingSignUpAdmin(admin.ModelAdmin): 23 | list_display = ['__unicode__', 'timestamp'] 24 | class Meta: 25 | model = EmailMarketingSignUp 26 | 27 | 28 | admin.site.register(EmailMarketingSignUp, EmailMarketingSignUpAdmin) -------------------------------------------------------------------------------- /ecommerce/accounts/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from django.contrib.auth import get_user_model 4 | 5 | 6 | 7 | User = get_user_model() 8 | 9 | from .models import UserAddress 10 | 11 | 12 | class UserAddressForm(forms.ModelForm): 13 | default = forms.BooleanField(label='Make Default') 14 | class Meta: 15 | model = UserAddress 16 | fields = ["address", 17 | "address2", 18 | "city", 19 | "state", 20 | "country", 21 | "zipcode", 22 | "phone"] 23 | 24 | 25 | 26 | class LoginForm(forms.Form): 27 | username = forms.CharField() 28 | password = forms.CharField(widget=forms.PasswordInput()) 29 | 30 | def clean_username(self): 31 | username = self.cleaned_data.get("username") 32 | try: 33 | user = User.objects.get(username=username) 34 | except User.DoesNotExist: 35 | raise forms.ValidationError("Are you sure you are registered? We cannot find this user.") 36 | return username 37 | 38 | def clean_password(self): 39 | username = self.cleaned_data.get("username") 40 | password = self.cleaned_data.get("password") 41 | try: 42 | user = User.objects.get(username=username) 43 | except: 44 | user = None 45 | if user is not None and not user.check_password(password): 46 | raise forms.ValidationError("Invalid Password") 47 | elif user is None: 48 | pass 49 | else: 50 | return password 51 | 52 | 53 | class RegistrationForm(forms.ModelForm): 54 | email = forms.EmailField(label='Your Email') 55 | password1 = forms.CharField(label='Password', \ 56 | widget=forms.PasswordInput()) 57 | password2 = forms.CharField(label='Password Confirmation', \ 58 | widget=forms.PasswordInput()) 59 | 60 | class Meta: 61 | model = User 62 | fields = ['username', 'email'] 63 | 64 | def clean_password2(self): 65 | password1 = self.cleaned_data.get('password1') 66 | password2 = self.cleaned_data.get('password2') 67 | if password1 and password2 and password1 != password2: 68 | raise forms.ValidationError("Passwords do not match") 69 | return password2 70 | 71 | def clean_email(self): 72 | email = self.cleaned_data.get("email") 73 | user_count = User.objects.filter(email=email).count() 74 | if user_count > 0: 75 | raise forms.ValidationError("This email has already been registered. Please check and try again or reset your password.") 76 | return email 77 | 78 | 79 | def save(self, commit=True): 80 | user = super(RegistrationForm, self).save(commit=False) 81 | user.set_password(self.cleaned_data['password1']) 82 | if commit: 83 | user.save() 84 | return user 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /ecommerce/accounts/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.conf import settings 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='EmailConfirmed', 17 | fields=[ 18 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 19 | ('activation_key', models.CharField(max_length=200)), 20 | ('confirmed', models.BooleanField(default=False)), 21 | ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)), 22 | ], 23 | options={ 24 | }, 25 | bases=(models.Model,), 26 | ), 27 | migrations.CreateModel( 28 | name='EmailMarketingSignUp', 29 | fields=[ 30 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 31 | ('email', models.EmailField(max_length=75)), 32 | ('timestamp', models.DateTimeField(auto_now_add=True)), 33 | ('updated', models.DateTimeField(auto_now=True)), 34 | ], 35 | options={ 36 | }, 37 | bases=(models.Model,), 38 | ), 39 | migrations.CreateModel( 40 | name='UserAddress', 41 | fields=[ 42 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 43 | ('address', models.CharField(max_length=120)), 44 | ('address2', models.CharField(max_length=120, null=True, blank=True)), 45 | ('city', models.CharField(max_length=120)), 46 | ('state', models.CharField(blank=True, max_length=120, null=True, choices=[(b'AL', b'Alabama'), (b'AK', b'Alaska'), (b'AZ', b'Arizona'), (b'AR', b'Arkansas'), (b'CA', b'California'), (b'CO', b'Colorado'), (b'CT', b'Connecticut'), (b'DE', b'Delaware'), (b'DC', b'District of Columbia'), (b'FL', b'Florida'), (b'GA', b'Georgia'), (b'HI', b'Hawaii'), (b'ID', b'Idaho'), (b'IL', b'Illinois'), (b'IN', b'Indiana'), (b'IA', b'Iowa'), (b'KS', b'Kansas'), (b'KY', b'Kentucky'), (b'LA', b'Louisiana'), (b'ME', b'Maine'), (b'MD', b'Maryland'), (b'MA', b'Massachusetts'), (b'MI', b'Michigan'), (b'MN', b'Minnesota'), (b'MS', b'Mississippi'), (b'MO', b'Missouri'), (b'MT', b'Montana'), (b'NE', b'Nebraska'), (b'NV', b'Nevada'), (b'NH', b'New Hampshire'), (b'NJ', b'New Jersey'), (b'NM', b'New Mexico'), (b'NY', b'New York'), (b'NC', b'North Carolina'), (b'ND', b'North Dakota'), (b'OH', b'Ohio'), (b'OK', b'Oklahoma'), (b'OR', b'Oregon'), (b'PA', b'Pennsylvania'), (b'RI', b'Rhode Island'), (b'SC', b'South Carolina'), (b'SD', b'South Dakota'), (b'TN', b'Tennessee'), (b'TX', b'Texas'), (b'UT', b'Utah'), (b'VT', b'Vermont'), (b'VA', b'Virginia'), (b'WA', b'Washington'), (b'WV', b'West Virginia'), (b'WI', b'Wisconsin'), (b'WY', b'Wyoming')])), 47 | ('country', models.CharField(max_length=120)), 48 | ('zipcode', models.CharField(max_length=25)), 49 | ('phone', models.CharField(max_length=120)), 50 | ('shipping', models.BooleanField(default=True)), 51 | ('billing', models.BooleanField(default=False)), 52 | ('timestamp', models.DateTimeField(auto_now_add=True)), 53 | ('updated', models.DateTimeField(auto_now=True)), 54 | ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), 55 | ], 56 | options={ 57 | 'ordering': ['-updated', '-timestamp'], 58 | }, 59 | bases=(models.Model,), 60 | ), 61 | migrations.CreateModel( 62 | name='UserDefaultAddress', 63 | fields=[ 64 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 65 | ('billing', models.ForeignKey(related_name='user_address_billing_default', blank=True, to='accounts.UserAddress', null=True)), 66 | ('shipping', models.ForeignKey(related_name='user_address_shipping_default', blank=True, to='accounts.UserAddress', null=True)), 67 | ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)), 68 | ], 69 | options={ 70 | }, 71 | bases=(models.Model,), 72 | ), 73 | migrations.CreateModel( 74 | name='UserStripe', 75 | fields=[ 76 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 77 | ('stripe_id', models.CharField(max_length=120, null=True, blank=True)), 78 | ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)), 79 | ], 80 | options={ 81 | }, 82 | bases=(models.Model,), 83 | ), 84 | ] 85 | -------------------------------------------------------------------------------- /ecommerce/accounts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/ecommerce/accounts/migrations/__init__.py -------------------------------------------------------------------------------- /ecommerce/accounts/models.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from django.conf import settings 4 | from django.core.mail import send_mail 5 | from django.core.urlresolvers import reverse 6 | from django.db import models 7 | from django.template.loader import render_to_string 8 | 9 | from localflavor.us.us_states import US_STATES 10 | # Create your models here. 11 | 12 | 13 | # NEW_STATE = ( 14 | # ('AB', "ABC STATE"), 15 | # ('BC', "BC STATE"), 16 | # ) 17 | 18 | 19 | NEW_STATE = US_STATES 20 | 21 | class UserDefaultAddress(models.Model): 22 | user = models.OneToOneField(settings.AUTH_USER_MODEL) 23 | shipping = models.ForeignKey("UserAddress", null=True,\ 24 | blank=True, related_name="user_address_shipping_default") 25 | billing = models.ForeignKey("UserAddress", null=True,\ 26 | blank=True, related_name="user_address_billing_default") 27 | 28 | def __unicode__(self): 29 | return str(self.user.username) 30 | 31 | 32 | 33 | class UserAddressManager(models.Manager): 34 | def get_billing_addresses(self, user): 35 | return super(UserAddressManager, self).filter(billing=True).filter(user=user) 36 | 37 | class UserAddress(models.Model): 38 | user = models.ForeignKey(settings.AUTH_USER_MODEL) 39 | address = models.CharField(max_length=120) 40 | address2 = models.CharField(max_length=120, null=True, blank=True) 41 | city = models.CharField(max_length=120) 42 | state = models.CharField(max_length=120, choices=NEW_STATE, null=True, blank=True) 43 | country = models.CharField(max_length=120) 44 | zipcode = models.CharField(max_length=25) 45 | phone = models.CharField(max_length=120) 46 | shipping = models.BooleanField(default=True) 47 | billing = models.BooleanField(default=False) 48 | timestamp = models.DateTimeField(auto_now_add=True, auto_now=False) 49 | updated = models.DateTimeField(auto_now_add=False, auto_now=True) 50 | 51 | def __unicode__(self): 52 | return self.get_address() 53 | 54 | def get_address(self): 55 | return "%s, %s, %s, %s, %s" %(self.address, self.city, self.state, self.country, self.zipcode) 56 | 57 | objects = UserAddressManager() 58 | 59 | class Meta: 60 | ordering = ['-updated', '-timestamp'] 61 | 62 | 63 | 64 | class UserStripe(models.Model): 65 | user = models.OneToOneField(settings.AUTH_USER_MODEL) 66 | stripe_id = models.CharField(max_length=120, null=True, blank=True) 67 | 68 | def __unicode__(self): 69 | return str(self.stripe_id) 70 | 71 | #cus_4iMQyUSLNMrCxe 72 | ####cus_4iMT2JwLrE8sWx 73 | 74 | 75 | class EmailConfirmed(models.Model): 76 | user = models.OneToOneField(settings.AUTH_USER_MODEL) 77 | activation_key = models.CharField(max_length=200) 78 | confirmed = models.BooleanField(default=False) 79 | 80 | def __unicode__(self): 81 | return str(self.confirmed) 82 | 83 | def activate_user_email(self): 84 | #send email here & render a string 85 | activation_url = "%s%s" %(settings.SITE_URL, reverse("activation_view", args=[self.activation_key])) 86 | context = { 87 | "activation_key": self.activation_key, 88 | "activation_url": activation_url, 89 | "user": self.user.username, 90 | } 91 | message = render_to_string("accounts/activation_message.txt", context) 92 | subject = "Activate your Email" 93 | self.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) 94 | 95 | def email_user(self, subject, message, from_email=None, **kwargs): 96 | send_mail(subject, message, from_email, [self.user.email], kwargs) 97 | 98 | 99 | 100 | 101 | class EmailMarketingSignUp(models.Model): 102 | email = models.EmailField() 103 | timestamp = models.DateTimeField(auto_now_add=True, auto_now=False) 104 | updated = models.DateTimeField(auto_now_add=False, auto_now=True) 105 | #confirmed = models.BooleanField(default=False) 106 | 107 | def __unicode__(self): 108 | return str(self.email) 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /ecommerce/accounts/signals.py: -------------------------------------------------------------------------------- 1 | import stripe 2 | import random 3 | import hashlib 4 | 5 | from django.conf import settings 6 | from django.contrib.auth.signals import user_logged_in 7 | from django.db.models.signals import post_save 8 | 9 | 10 | 11 | 12 | from .models import UserStripe, EmailConfirmed 13 | 14 | stripe.api_key = settings.STRIPE_SECRET_KEY 15 | 16 | 17 | 18 | # if we decide to change how we want to add the stripe id, this is where we set it for a user_logged_in 19 | # def get_or_create_stripe(sender, user, *args, **kwargs): 20 | # try: 21 | # user.userstripe.stripe_id 22 | # except UserStripe.DoesNotExist: 23 | # customer = stripe.Customer.create( 24 | # email = str(user.email) 25 | # ) 26 | # new_user_stripe = UserStripe.objects.create( 27 | # user=user, 28 | # stripe_id = customer.id 29 | # ) 30 | # except: 31 | # pass 32 | # # user_logged_in.connect(get_or_create_stripe) 33 | 34 | def get_create_stripe(user): 35 | new_user_stripe, created = UserStripe.objects.get_or_create(user=user) 36 | if created: 37 | customer = stripe.Customer.create( 38 | email = str(user.email) 39 | ) 40 | new_user_stripe.stripe_id = customer.id 41 | new_user_stripe.save() 42 | 43 | def user_created(sender, instance, created, *args, **kwargs): 44 | user = instance 45 | # 46 | if created: 47 | get_create_stripe(user) 48 | email_confirmed, email_is_created = EmailConfirmed.objects.get_or_create(user=user) 49 | if email_is_created: 50 | short_hash = hashlib.sha1(str(random.random())).hexdigest()[:5] 51 | base, domain = str(user.email).split("@") 52 | activation_key = hashlib.sha1(short_hash+base).hexdigest() 53 | email_confirmed.activation_key = activation_key 54 | email_confirmed.save() 55 | email_confirmed.activate_user_email() 56 | 57 | 58 | 59 | post_save.connect(user_created, sender=settings.AUTH_USER_MODEL) 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /ecommerce/accounts/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /ecommerce/accounts/views.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from django.shortcuts import render, HttpResponseRedirect, Http404 4 | from django.contrib.auth import logout, login, authenticate 5 | from django.contrib import messages 6 | from django.core.urlresolvers import reverse 7 | 8 | 9 | from .forms import LoginForm, RegistrationForm, UserAddressForm 10 | from .models import EmailConfirmed, UserDefaultAddress 11 | # Create your views here. 12 | 13 | 14 | def logout_view(request): 15 | print "logging out" 16 | logout(request) 17 | messages.success(request, "Successfully Logged out. Feel free to login again." %(reverse("auth_login")), extra_tags='safe, abc') 18 | messages.warning(request, "There's a warning.") 19 | messages.error(request, "There's an error.") 20 | return HttpResponseRedirect('%s'%(reverse("auth_login"))) 21 | 22 | def login_view(request): 23 | form = LoginForm(request.POST or None) 24 | btn = "Login" 25 | if form.is_valid(): 26 | username = form.cleaned_data['username'] 27 | password = form.cleaned_data['password'] 28 | user = authenticate(username=username, password=password) 29 | login(request, user) 30 | messages.success(request, "Successfully Logged In. Welcome Back!") 31 | return HttpResponseRedirect("/") 32 | context = { 33 | "form": form, 34 | "submit_btn": btn, 35 | } 36 | return render(request, "form.html", context) 37 | 38 | 39 | def registration_view(request): 40 | form = RegistrationForm(request.POST or None) 41 | btn = "Join" 42 | if form.is_valid(): 43 | new_user = form.save(commit=False) 44 | # new_user.first_name = "Justin" this is where you can do stuff with the model form 45 | new_user.save() 46 | messages.success(request, "Successfully Registered. Please confirm your email now.") 47 | return HttpResponseRedirect("/") 48 | # username = form.cleaned_data['username'] 49 | # password = form.cleaned_data['password'] 50 | # user = authenticate(username=username, password=password) 51 | # login(request, user) 52 | 53 | context = { 54 | "form": form, 55 | "submit_btn": btn, 56 | } 57 | return render(request, "form.html", context) 58 | 59 | 60 | SHA1_RE = re.compile('^[a-f0-9]{40}$') 61 | 62 | def activation_view(request, activation_key): 63 | if SHA1_RE.search(activation_key): 64 | print "activation key is real" 65 | try: 66 | instance = EmailConfirmed.objects.get(activation_key=activation_key) 67 | except EmailConfirmed.DoesNotExist: 68 | instance = None 69 | messages.success(request, "There was an error with your request.") 70 | return HttpResponseRedirect("/") 71 | if instance is not None and not instance.confirmed: 72 | page_message = "Confirmation Successful! Welcome." 73 | instance.confirmed = True 74 | instance.activation_key = "Confirmed" 75 | instance.save() 76 | messages.success(request, "Successfully Confirmed! Please login.") 77 | elif instance is not None and instance.confirmed: 78 | page_message = "Already Confirmed" 79 | messages.success(request, "Already Confirmed.") 80 | else: 81 | page_message = "" 82 | 83 | context = {"page_message": page_message} 84 | return render(request, "accounts/activation_complete.html", context) 85 | else: 86 | raise Http404 87 | 88 | 89 | 90 | 91 | def add_user_address(request): 92 | print request.GET 93 | try: 94 | next_page = request.GET.get("next") 95 | except: 96 | next_page = None 97 | form = UserAddressForm(request.POST or None) 98 | if request.method == "POST": 99 | if form.is_valid(): 100 | new_address = form.save(commit=False) 101 | new_address.user = request.user 102 | new_address.save() 103 | is_default = form.cleaned_data["default"] 104 | if is_default: 105 | default_address, created = UserDefaultAddress.objects.get_or_create(user=request.user) 106 | default_address.shipping = new_address 107 | default_address.save() 108 | 109 | if next_page is not None: 110 | return HttpResponseRedirect(reverse(str(next_page))) 111 | 112 | submit_btn = "Save Address" 113 | form_title = "Add New Address" 114 | return render(request, "form.html", 115 | {"form": form, 116 | "submit_btn": submit_btn, 117 | "form_title": form_title, 118 | }) 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /ecommerce/carts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/ecommerce/carts/__init__.py -------------------------------------------------------------------------------- /ecommerce/carts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | 5 | from .models import Cart, CartItem 6 | 7 | class CartAdmin(admin.ModelAdmin): 8 | class Meta: 9 | model = Cart 10 | 11 | 12 | admin.site.register(Cart, CartAdmin) 13 | 14 | 15 | admin.site.register(CartItem) -------------------------------------------------------------------------------- /ecommerce/carts/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('products', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Cart', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 18 | ('total', models.DecimalField(default=0.0, max_digits=100, decimal_places=2)), 19 | ('timestamp', models.DateTimeField(auto_now_add=True)), 20 | ('updated', models.DateTimeField(auto_now=True)), 21 | ('active', models.BooleanField(default=True)), 22 | ], 23 | options={ 24 | }, 25 | bases=(models.Model,), 26 | ), 27 | migrations.CreateModel( 28 | name='CartItem', 29 | fields=[ 30 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 31 | ('quantity', models.IntegerField(default=1)), 32 | ('line_total', models.DecimalField(default=10.99, max_digits=1000, decimal_places=2)), 33 | ('notes', models.TextField(null=True, blank=True)), 34 | ('timestamp', models.DateTimeField(auto_now_add=True)), 35 | ('updated', models.DateTimeField(auto_now=True)), 36 | ('cart', models.ForeignKey(blank=True, to='carts.Cart', null=True)), 37 | ('product', models.ForeignKey(to='products.Product')), 38 | ('variations', models.ManyToManyField(to='products.Variation', null=True, blank=True)), 39 | ], 40 | options={ 41 | }, 42 | bases=(models.Model,), 43 | ), 44 | ] 45 | -------------------------------------------------------------------------------- /ecommerce/carts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/ecommerce/carts/migrations/__init__.py -------------------------------------------------------------------------------- /ecommerce/carts/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | from products.models import Product, Variation 5 | 6 | class CartItem(models.Model): 7 | cart = models.ForeignKey('Cart', null=True, blank=True) 8 | product = models.ForeignKey(Product) 9 | variations = models.ManyToManyField(Variation, null=True, blank=True) 10 | quantity = models.IntegerField(default=1) 11 | line_total = models.DecimalField(default=10.99, max_digits=1000, decimal_places=2) 12 | notes = models.TextField(null=True, blank=True) 13 | timestamp = models.DateTimeField(auto_now_add=True, auto_now=False) 14 | updated = models.DateTimeField(auto_now_add=False, auto_now=True) 15 | 16 | def __unicode__(self): 17 | try: 18 | return str(self.cart.id) 19 | except: 20 | return self.product.title 21 | 22 | 23 | class Cart(models.Model): 24 | total = models.DecimalField(max_digits=100, decimal_places=2, default=0.00) 25 | timestamp = models.DateTimeField(auto_now_add=True, auto_now=False) 26 | updated = models.DateTimeField(auto_now_add=False, auto_now=True) 27 | active = models.BooleanField(default=True) 28 | 29 | def __unicode__(self): 30 | return "Cart id: %s" %(self.id) -------------------------------------------------------------------------------- /ecommerce/carts/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /ecommerce/carts/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, HttpResponseRedirect 2 | from django.core.urlresolvers import reverse 3 | 4 | # Create your views here. 5 | 6 | from products.models import Product, Variation 7 | 8 | from .models import Cart, CartItem 9 | 10 | def view(request): 11 | try: 12 | the_id = request.session['cart_id'] 13 | cart = Cart.objects.get(id=the_id) 14 | except: 15 | the_id = None 16 | 17 | 18 | 19 | if the_id: 20 | new_total = 0.00 21 | for item in cart.cartitem_set.all(): 22 | line_total = float(item.product.price) * item.quantity 23 | new_total += line_total 24 | request.session['items_total'] = cart.cartitem_set.count() 25 | cart.total = new_total 26 | cart.save() 27 | context = {"cart": cart} 28 | else: 29 | empty_message = "Your Cart is Empty, please keep shopping." 30 | context = {"empty": True, "empty_message": empty_message} 31 | 32 | template = "cart/view.html" 33 | return render(request, template, context) 34 | 35 | 36 | def remove_from_cart(request, id): 37 | try: 38 | the_id = request.session['cart_id'] 39 | cart = Cart.objects.get(id=the_id) 40 | except: 41 | return HttpResponseRedirect(reverse("cart")) 42 | 43 | cartitem = CartItem.objects.get(id=id) 44 | #cartitem.delete() 45 | cartitem.cart = None 46 | cartitem.save() 47 | #send "success message" 48 | return HttpResponseRedirect(reverse("cart")) 49 | 50 | 51 | 52 | 53 | def add_to_cart(request, slug): 54 | request.session.set_expiry(120000) 55 | 56 | try: 57 | the_id = request.session['cart_id'] 58 | except: 59 | new_cart = Cart() 60 | new_cart.save() 61 | request.session['cart_id'] = new_cart.id 62 | the_id = new_cart.id 63 | 64 | cart = Cart.objects.get(id=the_id) 65 | 66 | try: 67 | product = Product.objects.get(slug=slug) 68 | except Product.DoesNotExist: 69 | pass 70 | except: 71 | pass 72 | 73 | product_var = [] #product variation 74 | if request.method == "POST": 75 | qty = request.POST['qty'] 76 | for item in request.POST: 77 | key = item 78 | val = request.POST[key] 79 | try: 80 | v = Variation.objects.get(product=product, category__iexact=key, title__iexact=val) 81 | product_var.append(v) 82 | except: 83 | pass 84 | cart_item = CartItem.objects.create(cart=cart, product=product) 85 | if len(product_var) > 0: 86 | cart_item.variations.add(*product_var) 87 | cart_item.quantity = qty 88 | cart_item.save() 89 | # success message 90 | return HttpResponseRedirect(reverse("cart")) 91 | #error message 92 | return HttpResponseRedirect(reverse("cart")) -------------------------------------------------------------------------------- /ecommerce/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/ecommerce/db.sqlite3 -------------------------------------------------------------------------------- /ecommerce/ecommerce/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/ecommerce/ecommerce/__init__.py -------------------------------------------------------------------------------- /ecommerce/ecommerce/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for ecommerce project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.6/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.6/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 14 | 15 | 16 | # Quick-start development settings - unsuitable for production 17 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ 18 | 19 | # SECURITY WARNING: keep the secret key used in production secret! 20 | SECRET_KEY = 'l8jyt^qbes)16fvzgx=t_kd3=0ch(&^x02x%rp#q71kewuz%%p' 21 | 22 | # SECURITY WARNING: don't run with debug turned on in production! 23 | DEBUG = True 24 | 25 | TEMPLATE_DEBUG = True 26 | 27 | ALLOWED_HOSTS = [] 28 | 29 | 30 | 31 | DEFAULT_FROM_EMAIL = "Coding For Entrepreneurs " 32 | 33 | try: 34 | from .email_settings import host, user, password 35 | EMAIL_HOST = host #"smtp.gmail.com" #"smtp.sendgrid.net" 36 | EMAIL_HOST_USER = user #"codingforentrepreneurs@gmail.com" 37 | EMAIL_HOST_PASSWORD = password #"password" 38 | EMAIL_PORT = 587 39 | EMAIL_USE_TLS = True 40 | except: 41 | pass 42 | 43 | SITE_URL = "http://cfestore.com" 44 | 45 | 46 | # Application definition 47 | 48 | INSTALLED_APPS = ( 49 | 'django.contrib.admin', 50 | 'django.contrib.auth', 51 | 'django.contrib.contenttypes', 52 | 'django.contrib.sessions', 53 | 'django.contrib.messages', 54 | 'django.contrib.staticfiles', 55 | #'south', no longer supported 56 | 'accounts', 57 | 'carts', 58 | 'marketing', 59 | 'orders', 60 | 'products', 61 | 'localflavor', 62 | ) 63 | 64 | MIDDLEWARE_CLASSES = ( 65 | 'django.contrib.sessions.middleware.SessionMiddleware', 66 | 'django.middleware.common.CommonMiddleware', 67 | 'django.middleware.csrf.CsrfViewMiddleware', 68 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 69 | 'django.contrib.messages.middleware.MessageMiddleware', 70 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 71 | 'marketing.middleware.DisplayMarketing', 72 | # 73 | ) 74 | 75 | ROOT_URLCONF = 'ecommerce.urls' 76 | 77 | WSGI_APPLICATION = 'ecommerce.wsgi.application' 78 | 79 | 80 | # Database 81 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases 82 | 83 | DATABASES = { 84 | 'default': { 85 | 'ENGINE': 'django.db.backends.sqlite3', 86 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 87 | } 88 | } 89 | 90 | # Internationalization 91 | # https://docs.djangoproject.com/en/1.6/topics/i18n/ 92 | 93 | LANGUAGE_CODE = 'en-us' 94 | 95 | TIME_ZONE = 'US/Pacific' 96 | 97 | USE_I18N = True 98 | 99 | USE_L10N = True 100 | 101 | USE_TZ = True 102 | 103 | MARKETING_HOURS_OFFSET = 3 104 | MARKETING_SECONDS_OFFSET = 0 105 | DEFAULT_TAX_RATE = 0.08 # 8% 106 | 107 | TEMPLATE_CONTEXT_PROCESSORS = ( 108 | "django.contrib.auth.context_processors.auth", 109 | "django.core.context_processors.debug", 110 | "django.core.context_processors.i18n", 111 | "django.core.context_processors.media", 112 | "django.core.context_processors.request", 113 | "django.core.context_processors.static", 114 | "django.core.context_processors.tz", 115 | "django.contrib.messages.context_processors.messages", 116 | ) 117 | 118 | # Static files (CSS, JavaScript, Images) 119 | # https://docs.djangoproject.com/en/1.6/howto/static-files/ 120 | 121 | STATIC_URL = '/static/' 122 | MEDIA_URL = '/media/' 123 | 124 | MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), "static", "media") 125 | #MEDIA_ROOT = '/Users/jmitch/Desktop/ecommerce/static/media/' 126 | 127 | STATIC_ROOT = os.path.join(os.path.dirname(BASE_DIR), "static", "static_root") 128 | 129 | STATICFILES_DIRS = ( 130 | os.path.join(os.path.dirname(BASE_DIR), "static", "static_files"), 131 | ) 132 | 133 | TEMPLATE_DIRS = ( 134 | os.path.join(BASE_DIR, 'templates'), 135 | ) 136 | 137 | 138 | STRIPE_SECRET_KEY = "sk_test_tXCtSORPdz4nrozcoOsiCy2A" 139 | STRIPE_PUBLISHABLE_KEY = "pk_test_giqz4Y9dhjdg6QtIUbuOBahj" 140 | 141 | -------------------------------------------------------------------------------- /ecommerce/ecommerce/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls import patterns, include, url 3 | from django.conf.urls.static import static 4 | from django.contrib import admin 5 | admin.autodiscover() 6 | 7 | urlpatterns = patterns('', 8 | # Examples: 9 | url(r'^$', 'products.views.home', name='home'), 10 | url(r'^s/$', 'products.views.search', name='search'), 11 | url(r'^products/$', 'products.views.all', name='products'), 12 | url(r'^products/(?P[\w-]+)/$', 'products.views.single', name='single_product'), 13 | url(r'^cart/(?P\d+)/$', 'carts.views.remove_from_cart', name='remove_from_cart'), 14 | url(r'^cart/(?P[\w-]+)/$', 'carts.views.add_to_cart', name='add_to_cart'), 15 | url(r'^cart/$', 'carts.views.view', name='cart'), 16 | url(r'^checkout/$', 'orders.views.checkout', name='checkout'), 17 | url(r'^orders/$', 'orders.views.orders', name='user_orders'), 18 | url(r'^ajax/dismiss_marketing_message/$', 'marketing.views.dismiss_marketing_message', name='dismiss_marketing_message'), 19 | url(r'^ajax/email_signup/$', 'marketing.views.email_signup', name='ajax_email_signup'), 20 | url(r'^ajax/add_user_address/$', 'accounts.views.add_user_address', name='ajax_add_user_address'), 21 | 22 | # url(r'^blog/', include('blog.urls')), 23 | #(?P.*) 24 | #(?P\d+) 25 | url(r'^admin/', include(admin.site.urls)), 26 | url(r'^accounts/logout/$', 'accounts.views.logout_view', name='auth_logout'), 27 | url(r'^accounts/login/$', 'accounts.views.login_view', name='auth_login'), 28 | url(r'^accounts/register/$', 'accounts.views.registration_view', name='auth_register'), 29 | url(r'^accounts/address/add/$', 'accounts.views.add_user_address', name='add_user_address'), 30 | url(r'^accounts/activate/(?P\w+)/$', 'accounts.views.activation_view', name='activation_view'), 31 | ) 32 | 33 | 34 | if settings.DEBUG: 35 | urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 36 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) -------------------------------------------------------------------------------- /ecommerce/ecommerce/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for ecommerce project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ecommerce.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /ecommerce/main: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/ecommerce/main -------------------------------------------------------------------------------- /ecommerce/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ecommerce.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /ecommerce/marketing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/ecommerce/marketing/__init__.py -------------------------------------------------------------------------------- /ecommerce/marketing/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | from .models import MarketingMessage, Slider 5 | 6 | 7 | class MarketingMessageAdmin(admin.ModelAdmin): 8 | list_display = ["__unicode__", "start_date", "end_date", "active", "featured"] 9 | class Meta: 10 | model = MarketingMessage 11 | 12 | admin.site.register(MarketingMessage, MarketingMessageAdmin) 13 | 14 | 15 | 16 | 17 | class SliderAdmin(admin.ModelAdmin): 18 | list_display = ["__unicode__", "order", "start_date", "end_date", "active", "featured"] 19 | list_editable = ["order", "start_date", "end_date"] 20 | class Meta: 21 | model = Slider 22 | 23 | admin.site.register(Slider, SliderAdmin) -------------------------------------------------------------------------------- /ecommerce/marketing/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | 4 | class EmailForm(forms.Form): 5 | email = forms.EmailField(max_length=200) -------------------------------------------------------------------------------- /ecommerce/marketing/middleware.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.utils import timezone 4 | 5 | from .models import MarketingMessage 6 | 7 | def is_offset_greater(time_string_offset): 8 | time1 = str(timezone.now())[:19] 9 | offset_time = time_string_offset[:19] #to remove extra, unnecessary numbers 10 | offset_time_formated = datetime.datetime.strptime(offset_time, "%Y-%m-%d %H:%M:%S" ) 11 | offset_time_tz_aware = timezone.make_aware(offset_time_formated, timezone.get_default_timezone()) 12 | now_time_formated = datetime.datetime.strptime(time1, "%Y-%m-%d %H:%M:%S" ) 13 | now_time_tz_aware = timezone.make_aware(now_time_formated, timezone.get_default_timezone()) 14 | return now_time_tz_aware > offset_time_tz_aware 15 | 16 | 17 | 18 | class DisplayMarketing(): 19 | def process_request(self, request): 20 | try: 21 | message_offset = request.session['dismiss_message_for'] #as string 22 | except: 23 | message_offset = None 24 | 25 | try: 26 | marketing_message = MarketingMessage.objects.get_featured_item().message 27 | except: 28 | marketing_message = False 29 | 30 | if message_offset is None: 31 | request.session['marketing_message'] = marketing_message 32 | elif message_offset is not None and is_offset_greater(message_offset): 33 | request.session['marketing_message'] = marketing_message 34 | else: 35 | try: 36 | del request.session['marketing_message'] 37 | except: 38 | pass -------------------------------------------------------------------------------- /ecommerce/marketing/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import marketing.models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='MarketingMessage', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 18 | ('message', models.CharField(max_length=120)), 19 | ('active', models.BooleanField(default=False)), 20 | ('featured', models.BooleanField(default=False)), 21 | ('timestamp', models.DateTimeField(auto_now_add=True)), 22 | ('updated', models.DateTimeField(auto_now=True)), 23 | ('start_date', models.DateTimeField(null=True, blank=True)), 24 | ('end_date', models.DateTimeField(null=True, blank=True)), 25 | ], 26 | options={ 27 | 'ordering': ['-start_date', '-end_date'], 28 | }, 29 | bases=(models.Model,), 30 | ), 31 | migrations.CreateModel( 32 | name='Slider', 33 | fields=[ 34 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 35 | ('image', models.ImageField(upload_to=marketing.models.slider_upload)), 36 | ('order', models.IntegerField(default=0)), 37 | ('url_link', models.CharField(max_length=250, null=True, blank=True)), 38 | ('header_text', models.CharField(max_length=120, null=True, blank=True)), 39 | ('text', models.CharField(max_length=120, null=True, blank=True)), 40 | ('active', models.BooleanField(default=False)), 41 | ('featured', models.BooleanField(default=False)), 42 | ('timestamp', models.DateTimeField(auto_now_add=True)), 43 | ('updated', models.DateTimeField(auto_now=True)), 44 | ('start_date', models.DateTimeField(null=True, blank=True)), 45 | ('end_date', models.DateTimeField(null=True, blank=True)), 46 | ], 47 | options={ 48 | 'ordering': ['order', '-start_date', '-end_date'], 49 | }, 50 | bases=(models.Model,), 51 | ), 52 | ] 53 | -------------------------------------------------------------------------------- /ecommerce/marketing/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/ecommerce/marketing/migrations/__init__.py -------------------------------------------------------------------------------- /ecommerce/marketing/models.py: -------------------------------------------------------------------------------- 1 | #import datetime 2 | 3 | from django.conf import settings 4 | from django.db import models 5 | from django.utils import timezone 6 | 7 | # Create your models here. 8 | 9 | class MarketingQueryset(models.query.QuerySet): 10 | def active(self): 11 | return self.filter(active=True) 12 | 13 | def featured(self): 14 | return self.filter(featured=True)\ 15 | .filter(start_date__lt=timezone.now())\ 16 | .filter(end_date__gte=timezone.now()) 17 | 18 | 19 | class MarketingManager(models.Manager): 20 | def get_queryset(self): 21 | return MarketingQueryset(self.model, using=self._db) 22 | 23 | def all(self): 24 | return self.get_queryset().active() 25 | 26 | def all_featured(self): 27 | return self.get_queryset().active().featured() 28 | 29 | def get_featured_item(self): 30 | try: 31 | return self.get_queryset().active().featured()[0] 32 | except: 33 | return None 34 | 35 | class MarketingMessage(models.Model): 36 | message = models.CharField(max_length=120) 37 | active = models.BooleanField(default=False) 38 | featured = models.BooleanField(default=False) 39 | timestamp = models.DateTimeField(auto_now_add=True, auto_now=False) 40 | updated = models.DateTimeField(auto_now_add=False, auto_now=True) 41 | start_date = models.DateTimeField(auto_now_add=False, auto_now=False, null=True, blank=True) 42 | end_date = models.DateTimeField(auto_now_add=False, auto_now=False, null=True, blank=True) 43 | 44 | objects = MarketingManager() 45 | 46 | def __unicode__(self): 47 | return str(self.message[:12]) 48 | 49 | class Meta: 50 | ordering = ['-start_date', '-end_date'] 51 | 52 | 53 | def slider_upload(instance, filename): 54 | return "images/marketing/slider/%s" %(filename) 55 | 56 | 57 | class Slider(models.Model): 58 | image = models.ImageField(upload_to=slider_upload) 59 | #image = models.FileField(upload_to=slider_upload) 60 | order = models.IntegerField(default=0) 61 | url_link = models.CharField(max_length=250, null=True, blank=True) 62 | header_text = models.CharField(max_length=120, null=True, blank=True) 63 | text = models.CharField(max_length=120, null=True, blank=True) 64 | active = models.BooleanField(default=False) 65 | featured = models.BooleanField(default=False) 66 | timestamp = models.DateTimeField(auto_now_add=True, auto_now=False) 67 | updated = models.DateTimeField(auto_now_add=False, auto_now=True) 68 | start_date = models.DateTimeField(auto_now_add=False, auto_now=False, null=True, blank=True) 69 | end_date = models.DateTimeField(auto_now_add=False, auto_now=False, null=True, blank=True) 70 | 71 | objects = MarketingManager() 72 | 73 | def __unicode__(self): 74 | return str(self.image) 75 | 76 | class Meta: 77 | ordering = ['order', '-start_date', '-end_date'] 78 | 79 | def get_image_url(self): 80 | return "%s/%s" %(settings.MEDIA_URL, self.image) 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /ecommerce/marketing/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /ecommerce/marketing/views.py: -------------------------------------------------------------------------------- 1 | import json 2 | import datetime 3 | 4 | from django.conf import settings 5 | from django.shortcuts import render, HttpResponse, Http404 6 | from django.http import HttpResponseBadRequest 7 | from django.utils import timezone 8 | 9 | from accounts.models import EmailMarketingSignUp 10 | 11 | from .forms import EmailForm 12 | # Create your views here. 13 | 14 | def dismiss_marketing_message(request): 15 | if request.is_ajax(): 16 | data = {"success": True} 17 | print data 18 | json_data = json.dumps(data) 19 | request.session['dismiss_message_for'] = str(timezone.now() +\ 20 | datetime.timedelta(hours=settings.MARKETING_HOURS_OFFSET,\ 21 | seconds=settings.MARKETING_SECONDS_OFFSET)) 22 | print json_data 23 | return HttpResponse(json_data, content_type='application/json') 24 | else: 25 | raise Http404 26 | 27 | 28 | def email_signup(request): 29 | if request.method == "POST": 30 | form = EmailForm(request.POST) 31 | if form.is_valid(): 32 | email = form.cleaned_data['email'] 33 | new_signup = EmailMarketingSignUp.objects.create(email=email) 34 | request.session['email_added_marketing'] = True 35 | return HttpResponse('Success %s' %(email)) 36 | if form.errors: 37 | json_data = json.dumps(form.errors) 38 | return HttpResponseBadRequest(json_data, content_type='application/json') 39 | else: 40 | raise Http404 41 | -------------------------------------------------------------------------------- /ecommerce/orders/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/ecommerce/orders/__init__.py -------------------------------------------------------------------------------- /ecommerce/orders/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | 5 | from .models import Order 6 | 7 | admin.site.register(Order) -------------------------------------------------------------------------------- /ecommerce/orders/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.conf import settings 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('accounts', '0001_initial'), 12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 13 | ('carts', '0001_initial'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Order', 19 | fields=[ 20 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 21 | ('order_id', models.CharField(default=b'ABC', unique=True, max_length=120)), 22 | ('status', models.CharField(default=b'Started', max_length=120, choices=[(b'Started', b'Started'), (b'Abandoned', b'Abandoned'), (b'Finished', b'Finished')])), 23 | ('sub_total', models.DecimalField(default=10.99, max_digits=1000, decimal_places=2)), 24 | ('tax_total', models.DecimalField(default=0.0, max_digits=1000, decimal_places=2)), 25 | ('final_total', models.DecimalField(default=10.99, max_digits=1000, decimal_places=2)), 26 | ('timestamp', models.DateTimeField(auto_now_add=True)), 27 | ('updated', models.DateTimeField(auto_now=True)), 28 | ('billing_address', models.ForeignKey(related_name='billing_address', default=1, to='accounts.UserAddress')), 29 | ('cart', models.ForeignKey(to='carts.Cart')), 30 | ('shipping_address', models.ForeignKey(related_name='shipping_address', default=1, to='accounts.UserAddress')), 31 | ('user', models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True)), 32 | ], 33 | options={ 34 | }, 35 | bases=(models.Model,), 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /ecommerce/orders/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/ecommerce/orders/migrations/__init__.py -------------------------------------------------------------------------------- /ecommerce/orders/models.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from django.conf import settings 3 | from django.db import models 4 | 5 | # Create your models here. 6 | from accounts.models import UserAddress 7 | from carts.models import Cart 8 | 9 | 10 | STATUS_CHOICES = ( 11 | ("Started", "Started"), 12 | ("Abandoned", "Abandoned"), 13 | ("Finished", "Finished"), 14 | ) 15 | 16 | #python tuples 17 | try: 18 | tax_rate = settings.DEFAULT_TAX_RATE 19 | except Exception, e: 20 | print str(e) 21 | raise NotImplementedError(str(e)) 22 | 23 | class Order(models.Model): 24 | user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True) 25 | order_id = models.CharField(max_length=120, default='ABC', unique=True) 26 | cart = models.ForeignKey(Cart) 27 | status = models.CharField(max_length=120, choices=STATUS_CHOICES, default="Started") 28 | shipping_address = models.ForeignKey(UserAddress, related_name='shipping_address', default=1) 29 | billing_address = models.ForeignKey(UserAddress, related_name='billing_address', default=1) 30 | sub_total = models.DecimalField(default=10.99, max_digits=1000, decimal_places=2) 31 | tax_total = models.DecimalField(default=0.00, max_digits=1000, decimal_places=2) 32 | final_total = models.DecimalField(default=10.99, max_digits=1000, decimal_places=2) 33 | timestamp = models.DateTimeField(auto_now_add=True, auto_now=False) 34 | updated = models.DateTimeField(auto_now_add=False, auto_now=True) 35 | 36 | def __unicode__(self): 37 | return self.order_id 38 | 39 | def get_final_amount(self): 40 | instance = Order.objects.get(id=self.id) 41 | two_places = Decimal(10) ** -2 42 | tax_rate_dec = Decimal("%s" %(tax_rate)) 43 | sub_total_dec = Decimal(self.sub_total) 44 | tax_total_dec = Decimal(tax_rate_dec * sub_total_dec).quantize(two_places) 45 | instance.tax_total = tax_total_dec 46 | instance.final_total = sub_total_dec + tax_total_dec 47 | instance.save() 48 | return instance.final_total -------------------------------------------------------------------------------- /ecommerce/orders/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /ecommerce/orders/utils.py: -------------------------------------------------------------------------------- 1 | import string 2 | import random 3 | 4 | 5 | from .models import Order 6 | 7 | 8 | def id_generator(size=6, chars=string.ascii_uppercase + string.digits): 9 | the_id = "".join(random.choice(chars) for x in range(size)) 10 | try: 11 | order = Order.objects.get(order_id=the_id) 12 | id_generator() 13 | except Order.DoesNotExist: 14 | return the_id -------------------------------------------------------------------------------- /ecommerce/orders/views.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import stripe 4 | 5 | from django.contrib.auth.decorators import login_required 6 | from django.core.urlresolvers import reverse 7 | from django.conf import settings 8 | from django.contrib import messages 9 | from django.shortcuts import render, HttpResponseRedirect 10 | 11 | # Create your views here. 12 | 13 | from accounts.forms import UserAddressForm 14 | from accounts.models import UserAddress 15 | from carts.models import Cart 16 | 17 | from .models import Order 18 | from .utils import id_generator 19 | 20 | try: 21 | stripe_pub = settings.STRIPE_PUBLISHABLE_KEY 22 | stripe_secret = settings.STRIPE_SECRET_KEY 23 | except Exception, e: 24 | print str(e) 25 | raise NotImplementedError(str(e)) 26 | 27 | 28 | stripe.api_key = stripe_secret 29 | 30 | 31 | def orders(request): 32 | context = {} 33 | template = "orders/user.html" 34 | return render(request, template, context) 35 | 36 | 37 | #require user login ** 38 | @login_required 39 | def checkout(request): 40 | try: 41 | the_id = request.session['cart_id'] 42 | cart = Cart.objects.get(id=the_id) 43 | except: 44 | the_id = None 45 | #return HttpResponseRedirect("/cart/") 46 | return HttpResponseRedirect(reverse("cart")) 47 | 48 | try: 49 | new_order = Order.objects.get(cart=cart) 50 | except Order.DoesNotExist: 51 | new_order = Order() 52 | new_order.cart = cart 53 | new_order.user = request.user 54 | new_order.order_id = id_generator() 55 | new_order.save() 56 | except: 57 | new_order = None 58 | # work on some error message 59 | return HttpResponseRedirect(reverse("cart")) 60 | final_amount = 0 61 | if new_order is not None: 62 | new_order.sub_total = cart.total 63 | new_order.save() 64 | final_amount = new_order.get_final_amount() 65 | 66 | try: 67 | address_added = request.GET.get("address_added") 68 | except: 69 | address_added = None 70 | 71 | if address_added is None: 72 | address_form = UserAddressForm() 73 | else: 74 | address_form = None 75 | 76 | current_addresses = UserAddress.objects.filter(user=request.user) 77 | billing_addresses = UserAddress.objects.get_billing_addresses(user=request.user) 78 | print billing_addresses 79 | ##1 add shipping address 80 | ##2 add billing address 81 | #3 add and run credit card 82 | 83 | if request.method == "POST": 84 | try: 85 | user_stripe = request.user.userstripe.stripe_id 86 | customer = stripe.Customer.retrieve(user_stripe) 87 | #print customer 88 | except: 89 | customer = None 90 | pass 91 | if customer is not None: 92 | billing_a = request.POST['billing_address'] 93 | shipping_a = request.POST['shipping_address'] 94 | token = request.POST['stripeToken'] 95 | 96 | try: 97 | billing_address_instance = UserAddress.objects.get(id=billing_a) 98 | except: 99 | billing_address_instance = None 100 | 101 | try: 102 | shipping_address_instance = UserAddress.objects.get(id=shipping_a) 103 | except: 104 | shipping_address_instance = None 105 | 106 | card = customer.cards.create(card=token) 107 | card.address_city = billing_address_instance.city or None 108 | card.address_line1 = billing_address_instance.address or None 109 | card.address_line2 = billing_address_instance.address2 or None 110 | card.address_state = billing_address_instance.state or None 111 | card.address_country = billing_address_instance.country or None 112 | card.address_zip = billing_address_instance.zipcode or None 113 | card.save() 114 | 115 | charge = stripe.Charge.create( 116 | amount= int(final_amount * 100), 117 | currency="usd", 118 | card = card, # obtained with Stripe.js 119 | customer = customer, 120 | description="Charge for %s" %(request.user.username) 121 | ) 122 | if charge["captured"]: 123 | new_order.status = "Finished" 124 | new_order.shipping_address = shipping_address_instance 125 | new_order.billing_addresses = billing_address_instance 126 | new_order.save() 127 | del request.session['cart_id'] 128 | del request.session['items_total'] 129 | messages.success(request, "Thank your order. It has been completed!") 130 | return HttpResponseRedirect(reverse("user_orders")) 131 | 132 | context = { 133 | "order": new_order, 134 | "address_form": address_form, 135 | "current_addresses": current_addresses, 136 | "billing_addresses": billing_addresses, 137 | "stripe_pub": stripe_pub, 138 | } 139 | template = "orders/checkout.html" 140 | return render(request, template, context) 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /ecommerce/products/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/ecommerce/products/__init__.py -------------------------------------------------------------------------------- /ecommerce/products/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | from .models import Product, ProductImage, Variation, Category 5 | 6 | class ProductAdmin(admin.ModelAdmin): 7 | date_hierarchy = 'timestamp' #updated 8 | search_fields = ['title', 'description'] 9 | list_display = ['title', 'price', 'active', 'updated'] 10 | list_editable = ['price', 'active'] 11 | list_filter = ['price', 'active'] 12 | readonly_fields = ['updated', 'timestamp'] 13 | prepopulated_fields = {"slug": ("title",)} 14 | class Meta: 15 | model = Product 16 | 17 | admin.site.register(Product, ProductAdmin) 18 | 19 | 20 | admin.site.register(ProductImage) 21 | admin.site.register(Variation) 22 | admin.site.register(Category) -------------------------------------------------------------------------------- /ecommerce/products/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='Category', 15 | fields=[ 16 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 17 | ('title', models.CharField(max_length=120)), 18 | ('description', models.TextField(null=True, blank=True)), 19 | ('slug', models.SlugField(unique=True)), 20 | ('featured', models.BooleanField(default=None)), 21 | ('timestamp', models.DateTimeField(auto_now_add=True)), 22 | ('updated', models.DateTimeField(auto_now=True)), 23 | ('active', models.BooleanField(default=True)), 24 | ], 25 | options={ 26 | }, 27 | bases=(models.Model,), 28 | ), 29 | migrations.CreateModel( 30 | name='Product', 31 | fields=[ 32 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 33 | ('title', models.CharField(max_length=120)), 34 | ('description', models.TextField(null=True, blank=True)), 35 | ('price', models.DecimalField(default=29.99, max_digits=100, decimal_places=2)), 36 | ('sale_price', models.DecimalField(null=True, max_digits=100, decimal_places=2, blank=True)), 37 | ('slug', models.SlugField(unique=True)), 38 | ('timestamp', models.DateTimeField(auto_now_add=True)), 39 | ('updated', models.DateTimeField(auto_now=True)), 40 | ('active', models.BooleanField(default=True)), 41 | ('update_defaults', models.BooleanField(default=False)), 42 | ('category', models.ManyToManyField(to='products.Category', null=True, blank=True)), 43 | ], 44 | options={ 45 | }, 46 | bases=(models.Model,), 47 | ), 48 | migrations.CreateModel( 49 | name='ProductImage', 50 | fields=[ 51 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 52 | ('image', models.ImageField(upload_to=b'products/images/')), 53 | ('featured', models.BooleanField(default=False)), 54 | ('thumbnail', models.BooleanField(default=False)), 55 | ('active', models.BooleanField(default=True)), 56 | ('updated', models.DateTimeField(auto_now=True)), 57 | ('product', models.ForeignKey(to='products.Product')), 58 | ], 59 | options={ 60 | }, 61 | bases=(models.Model,), 62 | ), 63 | migrations.CreateModel( 64 | name='Variation', 65 | fields=[ 66 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 67 | ('category', models.CharField(default=b'size', max_length=120, choices=[(b'size', b'size'), (b'color', b'color'), (b'package', b'package')])), 68 | ('title', models.CharField(max_length=120)), 69 | ('price', models.DecimalField(null=True, max_digits=100, decimal_places=2, blank=True)), 70 | ('updated', models.DateTimeField(auto_now=True)), 71 | ('active', models.BooleanField(default=True)), 72 | ('image', models.ForeignKey(blank=True, to='products.ProductImage', null=True)), 73 | ('product', models.ForeignKey(to='products.Product')), 74 | ], 75 | options={ 76 | }, 77 | bases=(models.Model,), 78 | ), 79 | migrations.AlterUniqueTogether( 80 | name='product', 81 | unique_together=set([('title', 'slug')]), 82 | ), 83 | ] 84 | -------------------------------------------------------------------------------- /ecommerce/products/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/ecommerce/products/migrations/__init__.py -------------------------------------------------------------------------------- /ecommerce/products/models.py: -------------------------------------------------------------------------------- 1 | from django.core.urlresolvers import reverse 2 | from django.db import models 3 | from django.db.models.signals import post_save 4 | # Create your models here. 5 | 6 | class Category(models.Model): 7 | title = models.CharField(max_length=120) 8 | description = models.TextField(null=True, blank=True) 9 | slug = models.SlugField(unique=True) 10 | featured = models.BooleanField(default=None) 11 | timestamp = models.DateTimeField(auto_now_add=True, auto_now=False) 12 | updated = models.DateTimeField(auto_now_add=False, auto_now=True) 13 | active = models.BooleanField(default=True) 14 | 15 | 16 | def __unicode__(self): 17 | return self.title 18 | 19 | # T-Shirt 1 20 | # Active Wear 2 21 | # Women's Clothing 3 22 | 23 | 24 | class Product(models.Model): 25 | title = models.CharField(max_length=120) 26 | description = models.TextField(null=True, blank=True) 27 | category = models.ManyToManyField(Category, null=True, blank=True) 28 | price = models.DecimalField(decimal_places=2, max_digits=100, default=29.99) 29 | sale_price = models.DecimalField(decimal_places=2, max_digits=100,\ 30 | null=True, blank=True) 31 | slug = models.SlugField(unique=True) 32 | timestamp = models.DateTimeField(auto_now_add=True, auto_now=False) 33 | updated = models.DateTimeField(auto_now_add=False, auto_now=True) 34 | active = models.BooleanField(default=True) 35 | update_defaults = models.BooleanField(default=False) 36 | 37 | def __unicode__(self): 38 | return self.title 39 | 40 | class Meta: 41 | unique_together = ('title', 'slug') 42 | 43 | def get_price(self): 44 | return self.price 45 | 46 | def get_absolute_url(self): 47 | return reverse("single_product", kwargs={"slug": self.slug}) 48 | 49 | 50 | class ProductImage(models.Model): 51 | product = models.ForeignKey(Product) 52 | image = models.ImageField(upload_to='products/images/') 53 | featured = models.BooleanField(default=False) 54 | thumbnail = models.BooleanField(default=False) 55 | active = models.BooleanField(default=True) 56 | updated = models.DateTimeField(auto_now_add=False, auto_now=True) 57 | 58 | def __unicode__(self): 59 | return self.product.title 60 | 61 | 62 | 63 | 64 | class VariationManager(models.Manager): 65 | def all(self): 66 | return super(VariationManager, self).filter(active=True) 67 | 68 | def sizes(self): 69 | return self.all().filter(category='size') 70 | 71 | def colors(self): 72 | return self.all().filter(category='color') 73 | 74 | 75 | VAR_CATEGORIES = ( 76 | ('size', 'size'), 77 | ('color', 'color'), 78 | ('package', 'package'), 79 | ) 80 | 81 | 82 | class Variation(models.Model): 83 | product = models.ForeignKey(Product) 84 | category = models.CharField(max_length=120, choices=VAR_CATEGORIES, default='size') 85 | title = models.CharField(max_length=120) 86 | image = models.ForeignKey(ProductImage, null=True, blank=True) 87 | price = models.DecimalField(max_digits=100, decimal_places=2, null=True, blank=True) 88 | updated = models.DateTimeField(auto_now_add=False, auto_now=True) 89 | active = models.BooleanField(default=True) 90 | 91 | objects = VariationManager() 92 | 93 | def __unicode__(self): 94 | return self.title 95 | 96 | 97 | 98 | 99 | def product_defaults(sender, instance, created, *args, **kwargs): 100 | if instance.update_defaults: 101 | categories = instance.category.all() 102 | print categories 103 | for cat in categories: 104 | print cat.id 105 | if cat.id == 1: #for t-shirts 106 | small_size = Variation.objects.get_or_create(product=instance, 107 | category='size', 108 | title='Small') 109 | medium_size = Variation.objects.get_or_create(product=instance, 110 | category='size', 111 | title='Medium') 112 | large_size = Variation.objects.get_or_create(product=instance, 113 | category='size', 114 | title='Large') 115 | instance.update_defaults = False 116 | instance.save() 117 | #print args, kwargs 118 | 119 | post_save.connect(product_defaults, sender=Product) 120 | -------------------------------------------------------------------------------- /ecommerce/products/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /ecommerce/products/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, Http404 2 | 3 | # Create your views here. 4 | 5 | from marketing.forms import EmailForm 6 | from marketing.models import MarketingMessage, Slider 7 | 8 | 9 | from .models import Product, ProductImage 10 | 11 | 12 | def search(request): 13 | try: 14 | q = request.GET.get('q') 15 | except: 16 | q = None 17 | 18 | if q: 19 | products = Product.objects.filter(title__icontains=q) 20 | context = {'query': q, 'products': products} 21 | template = 'products/results.html' 22 | else: 23 | template = 'products/home.html' 24 | context = {} 25 | return render(request, template, context) 26 | 27 | 28 | def home(request): 29 | sliders = Slider.objects.all_featured() 30 | products = Product.objects.all() 31 | template = 'products/home.html' 32 | context = { 33 | "products": products, 34 | "sliders": sliders, 35 | } 36 | return render(request, template, context) 37 | 38 | 39 | def all(request): 40 | products = Product.objects.all() 41 | context = {'products': products} 42 | template = 'products/all.html' 43 | return render(request, template, context) 44 | 45 | 46 | def single(request, slug): 47 | try: 48 | product = Product.objects.get(slug=slug) 49 | #images = product.productimage_set.all() 50 | images = ProductImage.objects.filter(product=product) 51 | context = {'product': product, "images": images} 52 | template = 'products/single.html' 53 | return render(request, template, context) 54 | except: 55 | raise Http404 56 | -------------------------------------------------------------------------------- /ecommerce/templates/accounts/activation_complete.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | 5 | 6 | 7 |

{{ page_message }}

8 | 9 | 10 | {% endblock %} -------------------------------------------------------------------------------- /ecommerce/templates/accounts/activation_message.txt: -------------------------------------------------------------------------------- 1 | Hello {{ user }}, 2 | 3 | Please activate your account here: 4 | 5 | {{ activation_url }} 6 | 7 | Thank you! 8 | 9 | -CFE -------------------------------------------------------------------------------- /ecommerce/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {% block head_title %}{% endblock %}CFE STORE 13 | 14 | {% load staticfiles %} 15 | 17 | 18 | 20 | 21 | 22 | 23 | 25 | 26 | 27 | 28 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 70 | 71 | 72 | 73 | 74 | {% if request.session.marketing_message %} 75 | 81 | {% endif %} 82 | 83 | {% include 'navbar.html' %} 84 | 85 | {% block jumbotron %} 86 | {% endblock %} 87 | 88 | 89 | 90 | 91 |
92 | {% if messages %} 93 | {% for message in messages %} 94 | 100 | {% endfor %} 101 | {% endif %} 102 | {% block content %} 103 | 104 | {% endblock %} 105 | 106 |
107 | 108 | {% include "modal.html" %} 109 | 110 | 111 | 113 | 114 | 115 | 116 | 117 | 173 | 174 | 175 | 181 | 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /ecommerce/templates/cart/view.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | 4 | 5 | {% block content %} 6 |
7 | {% if empty %} 8 | 9 |

{{ empty_message }}

10 | 11 | {% else %} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {% for item in cart.cartitem_set.all %} 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {% endfor %} 37 | 38 |
ItemPriceQty
Total: {{ cart.total }}
{{ item.product }} {% if item.variations.all %}
    {% for subitem in item.variations.all %}
  • {{ subitem.category|capfirst}} : {{ subitem.title|capfirst }}{% endfor %}
    • {% endif %}
{{ item.product.price }}{{ item.quantity }}Remove
39 | 40 |
41 | Checkout 42 | {% endif %} 43 |
44 | {% endblock %} -------------------------------------------------------------------------------- /ecommerce/templates/form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | 5 | {% block content %} 6 | 7 |
8 |

{{ form_title }}

9 |
{% csrf_token %} 10 | {{ form.as_p }} 11 | 12 | 13 | 14 |
15 |
16 | 17 | {% endblock %} -------------------------------------------------------------------------------- /ecommerce/templates/modal.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecommerce/templates/navbar.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ecommerce/templates/orders/checkout.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 68 | 69 | 141 | 142 | 143 | {% block content %} 144 | 145 |
146 |
147 |

Current total: {{ order.get_final_amount }}

148 | Finalize Checkout 149 |
150 |
151 | 152 |
153 | 154 | 162 | 163 | 164 | 165 | 166 | 167 |
{% csrf_token %} 168 | {% if current_addresses %} 169 |
170 |

Shipping Addresses

171 | 172 | {% for address in current_addresses %} 173 | 174 | {% if request.user.userdefaultaddress.shipping.id == address.id %} 175 |
188 | {% endif %} 189 | 190 | 191 | 192 | 193 | 194 | 195 | {% if billing_addresses %} 196 |
197 |

Billing Addresses

198 | 199 | 200 | {% for address in billing_addresses %} 201 | 202 | {% if request.user.userdefaultaddress.billing.id == address.id %} 203 |
214 | {% endif %} 215 | 216 |
217 |

Credit Info

218 | 220 | 221 |
222 |
223 |
224 | Card Number 225 | 226 |
227 |
228 | CVC 229 | 230 | 231 |
232 | 233 |
234 | 235 |
236 |
237 | 238 |
239 | Expiration (MM/YYYY) 240 |
241 | 242 |
243 | 244 |
245 |
246 | 247 |
248 |
249 |
250 |
251 |
252 | 253 | 254 | 255 | 256 | 257 | 258 |
259 |
260 |
261 | {% endblock %} -------------------------------------------------------------------------------- /ecommerce/templates/orders/user.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block content %} 5 | 6 | 7 | {{ user.order_set.all }} 8 | 9 | 10 | {% endblock %} -------------------------------------------------------------------------------- /ecommerce/templates/products/all.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | 4 | {% load staticfiles %} 5 | 6 | {% block head_title %} 7 | Products || 8 | {% endblock %} 9 | 10 | {% block styles %} 11 | .jumbotron { 12 | /*color: red;*/ 13 | } 14 | {% endblock %} 15 | 16 | 17 | {# Comments here #} 18 | 19 | 20 | {% block content %} 21 | 22 | 23 | 24 | 25 |
26 | 27 | {% for product in products %} 28 |
29 | 30 | 31 | 32 |
33 | {% if product.productimage_set.all %} 34 | 35 | {% for item in product.productimage_set.all %} 36 | 37 | {% if item.featured %} 38 |
39 | 40 | 41 |
42 |
43 |
44 | 45 | {% endif %} 46 | 47 | 48 | 49 | {% endfor %} 50 | 51 | {% else %} 52 |
53 | 54 | 55 |
56 |
57 |
58 | {% endif %} 59 | 60 |
61 |

{{ product.title }}

62 |

{{ product.description|truncatewords:15}}

63 |

View Button

64 |
65 |
66 | 67 | 68 |
69 | 70 | {% cycle "" "" "" "" "" "


" %} 71 | {% endfor %} 72 | 73 |
74 | 75 | {% endblock %} 76 | 77 | -------------------------------------------------------------------------------- /ecommerce/templates/products/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block head_title %} 5 | Welcome to 6 | {% endblock %} 7 | 8 | {% block styles %} 9 | .jumbotron { 10 | /*color: red;*/ 11 | } 12 | {% endblock %} 13 | 14 | {% block jquery %} 15 | //alert("welcome!"); 16 | {% endblock%} 17 | 18 | {# Comments here #} 19 | 20 | 21 | {% block jumbotron2 %} 22 |
23 | 24 | 25 |
26 |

Hello {{ username_is }}

27 |

This example is a quick exercise to illustrate how the default, static and fixed to top navbar work. It includes the responsive CSS and HTML, so it also adapts to your viewport and device.

28 |

To see the difference between static and fixed top navbars, just scroll.

29 |

30 | View navbar docs » 31 |

32 |
33 |
34 | {% endblock %} 35 | 36 | 37 | {% block jumbotron %} 38 | {% if sliders %} 39 |
40 | 82 | 83 |
84 | 85 | {% endif %} 86 | {% endblock %} 87 | 88 | {% block content %} 89 | 90 | 91 | 92 | 93 |
94 | 95 | {% for product in products %} 96 |
97 | 98 | 99 | 100 |
101 | {% if product.productimage_set.all %} 102 | 103 | {% for item in product.productimage_set.all %} 104 | 105 | {% if item.featured %} 106 |
109 | 110 | 111 |
112 |
113 |
114 | 115 | {% endif %} 116 | 117 | 118 | 119 | {% endfor %} 120 | 121 | {% else %} 122 | 123 | {% endif %} 124 | 125 |
126 |

{{ product.title }}

127 |

{{ product.description|truncatewords:15}}

128 |

View Button

129 |
130 |
131 | 132 | 133 |
134 | 135 | {% cycle "" "" "


" %} 136 | {% endfor %} 137 | 138 |
139 | 140 | {% endblock %} 141 | 142 | -------------------------------------------------------------------------------- /ecommerce/templates/products/results.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load staticfiles %} 3 | 4 | 5 | {% block content %} 6 | 7 |

Searched for: {{ query }}

8 | 9 | {{ products }} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% for product in products %} 19 | 20 | 46 | 50 | 51 | {% endfor %} 52 | 53 | 54 | 55 | 56 |
Product
21 | 22 | {% if product.productimage_set.all %} 23 | {% for item in product.productimage_set.all %} 24 | {% if item.featured %} 25 | 26 | 30 | 31 |
32 |
33 | 34 | {% endif %} 35 | {% endfor %} 36 | 37 | {% else %} 38 | 39 |
40 |
41 | 42 | 43 | 44 | {% endif %} 45 |
47 | 48 | {{ product }} 49 |
57 | 58 | {% endblock %} -------------------------------------------------------------------------------- /ecommerce/templates/products/single.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | 6 | 7 | {% block content %} 8 | 9 | 10 |
11 |
12 | {% for img in images %} 13 | 14 | {% if img.featured %} 15 |
16 | 17 |
18 |
19 | {% endif %} 20 | {% endfor %} 21 | {% for img in images %} 22 | {% if not img.featured %} 23 |
24 | 25 | 26 | 27 |
28 | 29 | {% endif %} 30 | 31 | {% endfor %} 32 |
33 |
34 |
35 |

{{ product.title }} 36 |

37 |
38 |
39 |
{% csrf_token %} 40 | 41 | 42 | 43 | {% if product.variation_set.all %} 44 | 45 | 46 | {% if product.variation_set.sizes %} 47 | 52 | {% endif %} 53 | 54 | {% if product.variation_set.colors %} 55 | 60 | {% endif %} 61 | 62 | 63 | 64 | {% endif %} 65 | 66 |
67 |
68 |
69 |
70 | 71 | Price: {{ product.price }}
72 | Shipping: Free with $25+ purchase. 73 |
74 |
75 |
76 | {{ product.description|linebreaks }} 77 |
78 |
79 |
80 | 81 | {% endblock %} -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.7.1 2 | Pillow==2.5.0 3 | certifi==14.05.14 4 | django-localflavor==1.0 5 | pytz==2014.4 6 | requests==2.4.0 7 | stripe==1.19.0 -------------------------------------------------------------------------------- /static/media/images/marketing/slider/Screen Shot 2014-10-30 at 1.32.55 PM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/media/images/marketing/slider/Screen Shot 2014-10-30 at 1.32.55 PM.png -------------------------------------------------------------------------------- /static/media/images/marketing/slider/Screen Shot 2014-10-30 at 1.32.55 PM_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/media/images/marketing/slider/Screen Shot 2014-10-30 at 1.32.55 PM_1.png -------------------------------------------------------------------------------- /static/media/images/marketing/slider/seven-mile-beach-grand-cayman-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/media/images/marketing/slider/seven-mile-beach-grand-cayman-1.jpg -------------------------------------------------------------------------------- /static/media/images/marketing/slider/seven-mile-beach-grand-cayman-1_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/media/images/marketing/slider/seven-mile-beach-grand-cayman-1_1.jpg -------------------------------------------------------------------------------- /static/media/products/images/4001_102.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/media/products/images/4001_102.jpg -------------------------------------------------------------------------------- /static/media/products/images/Screen_Shot_2014-07-03_at_12.57.47_PM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/media/products/images/Screen_Shot_2014-07-03_at_12.57.47_PM.png -------------------------------------------------------------------------------- /static/media/products/images/Screen_Shot_2014-07-03_at_12.57.47_PM_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/media/products/images/Screen_Shot_2014-07-03_at_12.57.47_PM_1.png -------------------------------------------------------------------------------- /static/media/products/images/Screen_Shot_2014-07-03_at_12.57.47_PM_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/media/products/images/Screen_Shot_2014-07-03_at_12.57.47_PM_2.png -------------------------------------------------------------------------------- /static/media/products/images/T-Shirt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/media/products/images/T-Shirt.jpg -------------------------------------------------------------------------------- /static/media/products/images/placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/media/products/images/placeholder.jpg -------------------------------------------------------------------------------- /static/media/products/images/placeholder_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/media/products/images/placeholder_1.jpg -------------------------------------------------------------------------------- /static/media/products/images/search.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/media/products/images/search.jpg -------------------------------------------------------------------------------- /static/static_files/img/placeholder.svg: -------------------------------------------------------------------------------- 1 | 242x200 -------------------------------------------------------------------------------- /static/static_root/admin/css/changelists.css: -------------------------------------------------------------------------------- 1 | /* CHANGELISTS */ 2 | 3 | #changelist { 4 | position: relative; 5 | width: 100%; 6 | } 7 | 8 | #changelist table { 9 | width: 100%; 10 | } 11 | 12 | .change-list .hiddenfields { display:none; } 13 | 14 | .change-list .filtered table { 15 | border-right: 1px solid #ddd; 16 | } 17 | 18 | .change-list .filtered { 19 | min-height: 400px; 20 | } 21 | 22 | .change-list .filtered { 23 | background: white url(../img/changelist-bg.gif) top right repeat-y !important; 24 | } 25 | 26 | .change-list .filtered .results, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { 27 | margin-right: 160px !important; 28 | width: auto !important; 29 | } 30 | 31 | .change-list .filtered table tbody th { 32 | padding-right: 1em; 33 | } 34 | 35 | #changelist-form .results { 36 | overflow-x: auto; 37 | } 38 | 39 | #changelist .toplinks { 40 | border-bottom: 1px solid #ccc !important; 41 | } 42 | 43 | #changelist .paginator { 44 | color: #666; 45 | border-top: 1px solid #eee; 46 | border-bottom: 1px solid #eee; 47 | background: white url(../img/nav-bg.gif) 0 180% repeat-x; 48 | overflow: hidden; 49 | } 50 | 51 | .change-list .filtered .paginator { 52 | border-right: 1px solid #ddd; 53 | } 54 | 55 | /* CHANGELIST TABLES */ 56 | 57 | #changelist table thead th { 58 | padding: 0; 59 | white-space: nowrap; 60 | vertical-align: middle; 61 | } 62 | 63 | #changelist table thead th.action-checkbox-column { 64 | width: 1.5em; 65 | text-align: center; 66 | } 67 | 68 | #changelist table tbody td, #changelist table tbody th { 69 | border-left: 1px solid #ddd; 70 | } 71 | 72 | #changelist table tbody td:first-child, #changelist table tbody th:first-child { 73 | border-left: 0; 74 | border-right: 1px solid #ddd; 75 | } 76 | 77 | #changelist table tbody td.action-checkbox { 78 | text-align:center; 79 | } 80 | 81 | #changelist table tfoot { 82 | color: #666; 83 | } 84 | 85 | /* TOOLBAR */ 86 | 87 | #changelist #toolbar { 88 | padding: 3px; 89 | border-bottom: 1px solid #ddd; 90 | background: #e1e1e1 url(../img/nav-bg.gif) top left repeat-x; 91 | color: #666; 92 | } 93 | 94 | #changelist #toolbar form input { 95 | font-size: 11px; 96 | padding: 1px 2px; 97 | } 98 | 99 | #changelist #toolbar form #searchbar { 100 | padding: 2px; 101 | } 102 | 103 | #changelist #changelist-search img { 104 | vertical-align: middle; 105 | } 106 | 107 | /* FILTER COLUMN */ 108 | 109 | #changelist-filter { 110 | position: absolute; 111 | top: 0; 112 | right: 0; 113 | z-index: 1000; 114 | width: 160px; 115 | border-left: 1px solid #ddd; 116 | background: #efefef; 117 | margin: 0; 118 | } 119 | 120 | #changelist-filter h2 { 121 | font-size: 11px; 122 | padding: 2px 5px; 123 | border-bottom: 1px solid #ddd; 124 | } 125 | 126 | #changelist-filter h3 { 127 | font-size: 12px; 128 | margin-bottom: 0; 129 | } 130 | 131 | #changelist-filter ul { 132 | padding-left: 0; 133 | margin-left: 10px; 134 | } 135 | 136 | #changelist-filter li { 137 | list-style-type: none; 138 | margin-left: 0; 139 | padding-left: 0; 140 | } 141 | 142 | #changelist-filter a { 143 | color: #999; 144 | } 145 | 146 | #changelist-filter a:hover { 147 | color: #036; 148 | } 149 | 150 | #changelist-filter li.selected { 151 | border-left: 5px solid #ccc; 152 | padding-left: 5px; 153 | margin-left: -10px; 154 | } 155 | 156 | #changelist-filter li.selected a { 157 | color: #5b80b2 !important; 158 | } 159 | 160 | /* DATE DRILLDOWN */ 161 | 162 | .change-list ul.toplinks { 163 | display: block; 164 | background: white url(../img/nav-bg-reverse.gif) 0 -10px repeat-x; 165 | border-top: 1px solid white; 166 | float: left; 167 | padding: 0 !important; 168 | margin: 0 !important; 169 | width: 100%; 170 | } 171 | 172 | .change-list ul.toplinks li { 173 | padding: 3px 6px; 174 | font-weight: bold; 175 | list-style-type: none; 176 | display: inline-block; 177 | } 178 | 179 | .change-list ul.toplinks .date-back a { 180 | color: #999; 181 | } 182 | 183 | .change-list ul.toplinks .date-back a:hover { 184 | color: #036; 185 | } 186 | 187 | /* PAGINATOR */ 188 | 189 | .paginator { 190 | font-size: 11px; 191 | padding-top: 10px; 192 | padding-bottom: 10px; 193 | line-height: 22px; 194 | margin: 0; 195 | border-top: 1px solid #ddd; 196 | } 197 | 198 | .paginator a:link, .paginator a:visited { 199 | padding: 2px 6px; 200 | border: solid 1px #ccc; 201 | background: white; 202 | text-decoration: none; 203 | } 204 | 205 | .paginator a.showall { 206 | padding: 0 !important; 207 | border: none !important; 208 | } 209 | 210 | .paginator a.showall:hover { 211 | color: #036 !important; 212 | background: transparent !important; 213 | } 214 | 215 | .paginator .end { 216 | border-width: 2px !important; 217 | margin-right: 6px; 218 | } 219 | 220 | .paginator .this-page { 221 | padding: 2px 6px; 222 | font-weight: bold; 223 | font-size: 13px; 224 | vertical-align: top; 225 | } 226 | 227 | .paginator a:hover { 228 | color: white; 229 | background: #5b80b2; 230 | border-color: #036; 231 | } 232 | 233 | /* ACTIONS */ 234 | 235 | .filtered .actions { 236 | margin-right: 160px !important; 237 | border-right: 1px solid #ddd; 238 | } 239 | 240 | #changelist table input { 241 | margin: 0; 242 | } 243 | 244 | #changelist table tbody tr.selected { 245 | background-color: #FFFFCC; 246 | } 247 | 248 | #changelist .actions { 249 | color: #999; 250 | padding: 3px; 251 | border-top: 1px solid #fff; 252 | border-bottom: 1px solid #ddd; 253 | background: white url(../img/nav-bg-reverse.gif) 0 -10px repeat-x; 254 | } 255 | 256 | #changelist .actions.selected { 257 | background: #fffccf; 258 | border-top: 1px solid #fffee8; 259 | border-bottom: 1px solid #edecd6; 260 | } 261 | 262 | #changelist .actions span.all, 263 | #changelist .actions span.action-counter, 264 | #changelist .actions span.clear, 265 | #changelist .actions span.question { 266 | font-size: 11px; 267 | margin: 0 0.5em; 268 | display: none; 269 | } 270 | 271 | #changelist .actions:last-child { 272 | border-bottom: none; 273 | } 274 | 275 | #changelist .actions select { 276 | border: 1px solid #aaa; 277 | margin-left: 0.5em; 278 | padding: 1px 2px; 279 | } 280 | 281 | #changelist .actions label { 282 | font-size: 11px; 283 | margin-left: 0.5em; 284 | } 285 | 286 | #changelist #action-toggle { 287 | display: none; 288 | } 289 | 290 | #changelist .actions .button { 291 | font-size: 11px; 292 | padding: 1px 2px; 293 | } 294 | -------------------------------------------------------------------------------- /static/static_root/admin/css/dashboard.css: -------------------------------------------------------------------------------- 1 | /* DASHBOARD */ 2 | 3 | .dashboard .module table th { 4 | width: 100%; 5 | } 6 | 7 | .dashboard .module table td { 8 | white-space: nowrap; 9 | } 10 | 11 | .dashboard .module table td a { 12 | display: block; 13 | padding-right: .6em; 14 | } 15 | 16 | /* RECENT ACTIONS MODULE */ 17 | 18 | .module ul.actionlist { 19 | margin-left: 0; 20 | } 21 | 22 | ul.actionlist li { 23 | list-style-type: none; 24 | } 25 | 26 | ul.actionlist li { 27 | overflow: hidden; 28 | text-overflow: ellipsis; 29 | -o-text-overflow: ellipsis; 30 | } 31 | -------------------------------------------------------------------------------- /static/static_root/admin/css/forms.css: -------------------------------------------------------------------------------- 1 | @import url('widgets.css'); 2 | 3 | /* FORM ROWS */ 4 | 5 | .form-row { 6 | overflow: hidden; 7 | padding: 8px 12px; 8 | font-size: 11px; 9 | border-bottom: 1px solid #eee; 10 | } 11 | 12 | .form-row img, .form-row input { 13 | vertical-align: middle; 14 | } 15 | 16 | form .form-row p { 17 | padding-left: 0; 18 | font-size: 11px; 19 | } 20 | 21 | /* FORM LABELS */ 22 | 23 | form h4 { 24 | margin: 0 !important; 25 | padding: 0 !important; 26 | border: none !important; 27 | } 28 | 29 | label { 30 | font-weight: normal !important; 31 | color: #666; 32 | font-size: 12px; 33 | } 34 | 35 | .required label, label.required { 36 | font-weight: bold !important; 37 | color: #333 !important; 38 | } 39 | 40 | /* RADIO BUTTONS */ 41 | 42 | form ul.radiolist li { 43 | list-style-type: none; 44 | } 45 | 46 | form ul.radiolist label { 47 | float: none; 48 | display: inline; 49 | } 50 | 51 | form ul.inline { 52 | margin-left: 0; 53 | padding: 0; 54 | } 55 | 56 | form ul.inline li { 57 | float: left; 58 | padding-right: 7px; 59 | } 60 | 61 | /* ALIGNED FIELDSETS */ 62 | 63 | .aligned label { 64 | display: block; 65 | padding: 3px 10px 0 0; 66 | float: left; 67 | width: 8em; 68 | word-wrap: break-word; 69 | } 70 | 71 | .aligned ul label { 72 | display: inline; 73 | float: none; 74 | width: auto; 75 | } 76 | 77 | .colMS .aligned .vLargeTextField, .colMS .aligned .vXMLLargeTextField { 78 | width: 350px; 79 | } 80 | 81 | form .aligned p, form .aligned ul { 82 | margin-left: 7em; 83 | padding-left: 30px; 84 | } 85 | 86 | form .aligned table p { 87 | margin-left: 0; 88 | padding-left: 0; 89 | } 90 | 91 | form .aligned p.help { 92 | padding-left: 38px; 93 | } 94 | 95 | .aligned .vCheckboxLabel { 96 | float: none !important; 97 | display: inline; 98 | padding-left: 4px; 99 | } 100 | 101 | .colM .aligned .vLargeTextField, .colM .aligned .vXMLLargeTextField { 102 | width: 610px; 103 | } 104 | 105 | .checkbox-row p.help { 106 | margin-left: 0; 107 | padding-left: 0 !important; 108 | } 109 | 110 | fieldset .field-box { 111 | float: left; 112 | margin-right: 20px; 113 | } 114 | 115 | /* WIDE FIELDSETS */ 116 | 117 | .wide label { 118 | width: 15em !important; 119 | } 120 | 121 | form .wide p { 122 | margin-left: 15em; 123 | } 124 | 125 | form .wide p.help { 126 | padding-left: 38px; 127 | } 128 | 129 | .colM fieldset.wide .vLargeTextField, .colM fieldset.wide .vXMLLargeTextField { 130 | width: 450px; 131 | } 132 | 133 | /* COLLAPSED FIELDSETS */ 134 | 135 | fieldset.collapsed * { 136 | display: none; 137 | } 138 | 139 | fieldset.collapsed h2, fieldset.collapsed { 140 | display: block !important; 141 | } 142 | 143 | fieldset.collapsed h2 { 144 | background-image: url(../img/nav-bg.gif); 145 | background-position: bottom left; 146 | color: #999; 147 | } 148 | 149 | fieldset.collapsed .collapse-toggle { 150 | background: transparent; 151 | display: inline !important; 152 | } 153 | 154 | /* MONOSPACE TEXTAREAS */ 155 | 156 | fieldset.monospace textarea { 157 | font-family: "Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace; 158 | } 159 | 160 | /* SUBMIT ROW */ 161 | 162 | .submit-row { 163 | padding: 5px 7px; 164 | text-align: right; 165 | background: white url(../img/nav-bg.gif) 0 100% repeat-x; 166 | border: 1px solid #ccc; 167 | margin: 5px 0; 168 | overflow: hidden; 169 | } 170 | 171 | body.popup .submit-row { 172 | overflow: auto; 173 | } 174 | 175 | .submit-row input { 176 | margin: 0 0 0 5px; 177 | } 178 | 179 | .submit-row p { 180 | margin: 0.3em; 181 | } 182 | 183 | .submit-row p.deletelink-box { 184 | float: left; 185 | } 186 | 187 | .submit-row .deletelink { 188 | background: url(../img/icon_deletelink.gif) 0 50% no-repeat; 189 | padding-left: 14px; 190 | } 191 | 192 | /* CUSTOM FORM FIELDS */ 193 | 194 | .vSelectMultipleField { 195 | vertical-align: top !important; 196 | } 197 | 198 | .vCheckboxField { 199 | border: none; 200 | } 201 | 202 | .vDateField, .vTimeField { 203 | margin-right: 2px; 204 | } 205 | 206 | .vURLField { 207 | width: 30em; 208 | } 209 | 210 | .vLargeTextField, .vXMLLargeTextField { 211 | width: 48em; 212 | } 213 | 214 | .flatpages-flatpage #id_content { 215 | height: 40.2em; 216 | } 217 | 218 | .module table .vPositiveSmallIntegerField { 219 | width: 2.2em; 220 | } 221 | 222 | .vTextField { 223 | width: 20em; 224 | } 225 | 226 | .vIntegerField { 227 | width: 5em; 228 | } 229 | 230 | .vBigIntegerField { 231 | width: 10em; 232 | } 233 | 234 | .vForeignKeyRawIdAdminField { 235 | width: 5em; 236 | } 237 | 238 | /* INLINES */ 239 | 240 | .inline-group { 241 | padding: 0; 242 | border: 1px solid #ccc; 243 | margin: 10px 0; 244 | } 245 | 246 | .inline-group .aligned label { 247 | width: 8em; 248 | } 249 | 250 | .inline-related { 251 | position: relative; 252 | } 253 | 254 | .inline-related h3 { 255 | margin: 0; 256 | color: #666; 257 | padding: 3px 5px; 258 | font-size: 11px; 259 | background: #e1e1e1 url(../img/nav-bg.gif) top left repeat-x; 260 | border-bottom: 1px solid #ddd; 261 | } 262 | 263 | .inline-related h3 span.delete { 264 | float: right; 265 | } 266 | 267 | .inline-related h3 span.delete label { 268 | margin-left: 2px; 269 | font-size: 11px; 270 | } 271 | 272 | .inline-related fieldset { 273 | margin: 0; 274 | background: #fff; 275 | border: none; 276 | width: 100%; 277 | } 278 | 279 | .inline-related fieldset.module h3 { 280 | margin: 0; 281 | padding: 2px 5px 3px 5px; 282 | font-size: 11px; 283 | text-align: left; 284 | font-weight: bold; 285 | background: #bcd; 286 | color: #fff; 287 | } 288 | 289 | .inline-group .tabular fieldset.module { 290 | border: none; 291 | border-bottom: 1px solid #ddd; 292 | } 293 | 294 | .inline-related.tabular fieldset.module table { 295 | width: 100%; 296 | } 297 | 298 | .last-related fieldset { 299 | border: none; 300 | } 301 | 302 | .inline-group .tabular tr.has_original td { 303 | padding-top: 2em; 304 | } 305 | 306 | .inline-group .tabular tr td.original { 307 | padding: 2px 0 0 0; 308 | width: 0; 309 | _position: relative; 310 | } 311 | 312 | .inline-group .tabular th.original { 313 | width: 0px; 314 | padding: 0; 315 | } 316 | 317 | .inline-group .tabular td.original p { 318 | position: absolute; 319 | left: 0; 320 | height: 1.1em; 321 | padding: 2px 7px; 322 | overflow: hidden; 323 | font-size: 9px; 324 | font-weight: bold; 325 | color: #666; 326 | _width: 700px; 327 | } 328 | 329 | .inline-group ul.tools { 330 | padding: 0; 331 | margin: 0; 332 | list-style: none; 333 | } 334 | 335 | .inline-group ul.tools li { 336 | display: inline; 337 | padding: 0 5px; 338 | } 339 | 340 | .inline-group div.add-row, 341 | .inline-group .tabular tr.add-row td { 342 | color: #666; 343 | padding: 3px 5px; 344 | border-bottom: 1px solid #ddd; 345 | background: #e1e1e1 url(../img/nav-bg.gif) top left repeat-x; 346 | } 347 | 348 | .inline-group .tabular tr.add-row td { 349 | padding: 4px 5px 3px; 350 | border-bottom: none; 351 | } 352 | 353 | .inline-group ul.tools a.add, 354 | .inline-group div.add-row a, 355 | .inline-group .tabular tr.add-row td a { 356 | background: url(../img/icon_addlink.gif) 0 50% no-repeat; 357 | padding-left: 14px; 358 | font-size: 11px; 359 | outline: 0; /* Remove dotted border around link */ 360 | } 361 | 362 | .empty-form { 363 | display: none; 364 | } 365 | -------------------------------------------------------------------------------- /static/static_root/admin/css/ie.css: -------------------------------------------------------------------------------- 1 | /* IE 6 & 7 */ 2 | 3 | /* Proper fixed width for dashboard in IE6 */ 4 | 5 | .dashboard #content { 6 | *width: 768px; 7 | } 8 | 9 | .dashboard #content-main { 10 | *width: 535px; 11 | } 12 | 13 | /* IE 6 ONLY */ 14 | 15 | /* Keep header from flowing off the page */ 16 | 17 | #container { 18 | _position: static; 19 | } 20 | 21 | /* Put the right sidebars back on the page */ 22 | 23 | .colMS #content-related { 24 | _margin-right: 0; 25 | _margin-left: 10px; 26 | _position: static; 27 | } 28 | 29 | /* Put the left sidebars back on the page */ 30 | 31 | .colSM #content-related { 32 | _margin-right: 10px; 33 | _margin-left: -115px; 34 | _position: static; 35 | } 36 | 37 | .form-row { 38 | _height: 1%; 39 | } 40 | 41 | /* Fix right margin for changelist filters in IE6 */ 42 | 43 | #changelist-filter ul { 44 | _margin-right: -10px; 45 | } 46 | 47 | /* IE ignores min-height, but treats height as if it were min-height */ 48 | 49 | .change-list .filtered { 50 | _height: 400px; 51 | } 52 | 53 | /* IE doesn't know alpha transparency in PNGs */ 54 | 55 | .inline-deletelink { 56 | background: transparent url(../img/inline-delete-8bit.png) no-repeat; 57 | } 58 | 59 | /* IE7 doesn't support inline-block */ 60 | .change-list ul.toplinks li { 61 | zoom: 1; 62 | *display: inline; 63 | } -------------------------------------------------------------------------------- /static/static_root/admin/css/login.css: -------------------------------------------------------------------------------- 1 | /* LOGIN FORM */ 2 | 3 | body.login { 4 | background: #eee; 5 | } 6 | 7 | .login #container { 8 | background: white; 9 | border: 1px solid #ccc; 10 | width: 28em; 11 | min-width: 300px; 12 | margin-left: auto; 13 | margin-right: auto; 14 | margin-top: 100px; 15 | } 16 | 17 | .login #content-main { 18 | width: 100%; 19 | } 20 | 21 | .login form { 22 | margin-top: 1em; 23 | } 24 | 25 | .login .form-row { 26 | padding: 4px 0; 27 | float: left; 28 | width: 100%; 29 | } 30 | 31 | .login .form-row label { 32 | padding-right: 0.5em; 33 | line-height: 2em; 34 | font-size: 1em; 35 | clear: both; 36 | color: #333; 37 | } 38 | 39 | .login .form-row #id_username, .login .form-row #id_password { 40 | clear: both; 41 | padding: 6px; 42 | width: 100%; 43 | -webkit-box-sizing: border-box; 44 | -moz-box-sizing: border-box; 45 | box-sizing: border-box; 46 | } 47 | 48 | .login span.help { 49 | font-size: 10px; 50 | display: block; 51 | } 52 | 53 | .login .submit-row { 54 | clear: both; 55 | padding: 1em 0 0 9.4em; 56 | } 57 | 58 | .login .password-reset-link { 59 | text-align: center; 60 | } 61 | -------------------------------------------------------------------------------- /static/static_root/admin/css/rtl.css: -------------------------------------------------------------------------------- 1 | body { 2 | direction: rtl; 3 | } 4 | 5 | /* LOGIN */ 6 | 7 | .login .form-row { 8 | float: right; 9 | } 10 | 11 | .login .form-row label { 12 | float: right; 13 | padding-left: 0.5em; 14 | padding-right: 0; 15 | text-align: left; 16 | } 17 | 18 | .login .submit-row { 19 | clear: both; 20 | padding: 1em 9.4em 0 0; 21 | } 22 | 23 | /* GLOBAL */ 24 | 25 | th { 26 | text-align: right; 27 | } 28 | 29 | .module h2, .module caption { 30 | text-align: right; 31 | } 32 | 33 | .addlink, .changelink { 34 | padding-left: 0px; 35 | padding-right: 12px; 36 | background-position: 100% 0.2em; 37 | } 38 | 39 | .deletelink { 40 | padding-left: 0px; 41 | padding-right: 12px; 42 | background-position: 100% 0.25em; 43 | } 44 | 45 | .object-tools { 46 | float: left; 47 | } 48 | 49 | thead th:first-child, 50 | tfoot td:first-child { 51 | border-left: 1px solid #ddd !important; 52 | } 53 | 54 | /* LAYOUT */ 55 | 56 | #user-tools { 57 | right: auto; 58 | left: 0; 59 | text-align: left; 60 | } 61 | 62 | div.breadcrumbs { 63 | text-align: right; 64 | } 65 | 66 | #content-main { 67 | float: right; 68 | } 69 | 70 | #content-related { 71 | float: left; 72 | margin-left: -19em; 73 | margin-right: auto; 74 | } 75 | 76 | .colMS { 77 | margin-left: 20em !important; 78 | margin-right: 10px !important; 79 | } 80 | 81 | /* SORTABLE TABLES */ 82 | 83 | table thead th.sorted .sortoptions { 84 | float: left; 85 | } 86 | 87 | thead th.sorted .text { 88 | padding-right: 0; 89 | padding-left: 42px; 90 | } 91 | 92 | /* dashboard styles */ 93 | 94 | .dashboard .module table td a { 95 | padding-left: .6em; 96 | padding-right: 12px; 97 | } 98 | 99 | /* changelists styles */ 100 | 101 | .change-list .filtered { 102 | background: white url(../img/changelist-bg_rtl.gif) top left repeat-y !important; 103 | } 104 | 105 | .change-list .filtered table { 106 | border-left: 1px solid #ddd; 107 | border-right: 0px none; 108 | } 109 | 110 | #changelist-filter { 111 | right: auto; 112 | left: 0; 113 | border-left: 0px none; 114 | border-right: 1px solid #ddd; 115 | } 116 | 117 | .change-list .filtered .results, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { 118 | margin-right: 0px !important; 119 | margin-left: 160px !important; 120 | } 121 | 122 | #changelist-filter li.selected { 123 | border-left: 0px none; 124 | padding-left: 0px; 125 | margin-left: 0; 126 | border-right: 5px solid #ccc; 127 | padding-right: 5px; 128 | margin-right: -10px; 129 | } 130 | 131 | .filtered .actions { 132 | border-left:1px solid #DDDDDD; 133 | margin-left:160px !important; 134 | border-right: 0 none; 135 | margin-right:0 !important; 136 | } 137 | 138 | #changelist table tbody td:first-child, #changelist table tbody th:first-child { 139 | border-right: 0; 140 | border-left: 1px solid #ddd; 141 | } 142 | 143 | /* FORMS */ 144 | 145 | .aligned label { 146 | padding: 0 0 3px 1em; 147 | float: right; 148 | } 149 | 150 | .submit-row { 151 | text-align: left 152 | } 153 | 154 | .submit-row p.deletelink-box { 155 | float: right; 156 | } 157 | 158 | .submit-row .deletelink { 159 | background: url(../img/icon_deletelink.gif) 0 50% no-repeat; 160 | padding-right: 14px; 161 | } 162 | 163 | .vDateField, .vTimeField { 164 | margin-left: 2px; 165 | } 166 | 167 | form ul.inline li { 168 | float: right; 169 | padding-right: 0; 170 | padding-left: 7px; 171 | } 172 | 173 | input[type=submit].default, .submit-row input.default { 174 | float: left; 175 | } 176 | 177 | fieldset .field-box { 178 | float: right; 179 | margin-left: 20px; 180 | margin-right: 0; 181 | } 182 | 183 | .errorlist li { 184 | background-position: 100% .3em; 185 | padding: 4px 25px 4px 5px; 186 | } 187 | 188 | .errornote { 189 | background-position: 100% .3em; 190 | padding: 4px 25px 4px 5px; 191 | } 192 | 193 | /* WIDGETS */ 194 | 195 | .calendarnav-previous { 196 | top: 0; 197 | left: auto; 198 | right: 0; 199 | } 200 | 201 | .calendarnav-next { 202 | top: 0; 203 | right: auto; 204 | left: 0; 205 | } 206 | 207 | .calendar caption, .calendarbox h2 { 208 | text-align: center; 209 | } 210 | 211 | .selector { 212 | float: right; 213 | } 214 | 215 | .selector .selector-filter { 216 | text-align: right; 217 | } 218 | 219 | .inline-deletelink { 220 | float: left; 221 | } 222 | 223 | /* MISC */ 224 | 225 | .inline-related h2, .inline-group h2 { 226 | text-align: right 227 | } 228 | 229 | .inline-related h3 span.delete { 230 | padding-right: 20px; 231 | padding-left: inherit; 232 | left: 10px; 233 | right: inherit; 234 | float:left; 235 | } 236 | 237 | .inline-related h3 span.delete label { 238 | margin-left: inherit; 239 | margin-right: 2px; 240 | } 241 | 242 | /* IE7 specific bug fixes */ 243 | 244 | div.colM { 245 | position: relative; 246 | } 247 | 248 | .submit-row input { 249 | float: left; 250 | } -------------------------------------------------------------------------------- /static/static_root/admin/img/changelist-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/changelist-bg.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/changelist-bg_rtl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/changelist-bg_rtl.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/chooser-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/chooser-bg.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/chooser_stacked-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/chooser_stacked-bg.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/default-bg-reverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/default-bg-reverse.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/default-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/default-bg.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/deleted-overlay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/deleted-overlay.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/gis/move_vertex_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/gis/move_vertex_off.png -------------------------------------------------------------------------------- /static/static_root/admin/img/gis/move_vertex_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/gis/move_vertex_on.png -------------------------------------------------------------------------------- /static/static_root/admin/img/icon-no.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/icon-no.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/icon-unknown.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/icon-unknown.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/icon-yes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/icon-yes.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/icon_addlink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/icon_addlink.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/icon_alert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/icon_alert.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/icon_calendar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/icon_calendar.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/icon_changelink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/icon_changelink.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/icon_clock.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/icon_clock.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/icon_deletelink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/icon_deletelink.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/icon_error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/icon_error.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/icon_searchbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/icon_searchbox.png -------------------------------------------------------------------------------- /static/static_root/admin/img/icon_success.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/icon_success.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/inline-delete-8bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/inline-delete-8bit.png -------------------------------------------------------------------------------- /static/static_root/admin/img/inline-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/inline-delete.png -------------------------------------------------------------------------------- /static/static_root/admin/img/inline-restore-8bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/inline-restore-8bit.png -------------------------------------------------------------------------------- /static/static_root/admin/img/inline-restore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/inline-restore.png -------------------------------------------------------------------------------- /static/static_root/admin/img/inline-splitter-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/inline-splitter-bg.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/nav-bg-grabber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/nav-bg-grabber.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/nav-bg-reverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/nav-bg-reverse.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/nav-bg-selected.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/nav-bg-selected.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/nav-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/nav-bg.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/selector-icons.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/selector-icons.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/selector-search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/selector-search.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/sorting-icons.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/sorting-icons.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/tool-left.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/tool-left.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/tool-left_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/tool-left_over.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/tool-right.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/tool-right.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/tool-right_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/tool-right_over.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/tooltag-add.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/tooltag-add.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/tooltag-add_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/tooltag-add_over.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/tooltag-arrowright.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/tooltag-arrowright.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/tooltag-arrowright_over.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/open-ecommerce/af4745f24268a36467a261b302c3f7b5a43fa9c6/static/static_root/admin/img/tooltag-arrowright_over.gif -------------------------------------------------------------------------------- /static/static_root/admin/js/LICENSE-JQUERY.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 John Resig, http://jquery.com/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /static/static_root/admin/js/SelectBox.js: -------------------------------------------------------------------------------- 1 | var SelectBox = { 2 | cache: new Object(), 3 | init: function(id) { 4 | var box = document.getElementById(id); 5 | var node; 6 | SelectBox.cache[id] = new Array(); 7 | var cache = SelectBox.cache[id]; 8 | for (var i = 0; (node = box.options[i]); i++) { 9 | cache.push({value: node.value, text: node.text, displayed: 1}); 10 | } 11 | }, 12 | redisplay: function(id) { 13 | // Repopulate HTML select box from cache 14 | var box = document.getElementById(id); 15 | box.options.length = 0; // clear all options 16 | for (var i = 0, j = SelectBox.cache[id].length; i < j; i++) { 17 | var node = SelectBox.cache[id][i]; 18 | if (node.displayed) { 19 | box.options[box.options.length] = new Option(node.text, node.value, false, false); 20 | } 21 | } 22 | }, 23 | filter: function(id, text) { 24 | // Redisplay the HTML select box, displaying only the choices containing ALL 25 | // the words in text. (It's an AND search.) 26 | var tokens = text.toLowerCase().split(/\s+/); 27 | var node, token; 28 | for (var i = 0; (node = SelectBox.cache[id][i]); i++) { 29 | node.displayed = 1; 30 | for (var j = 0; (token = tokens[j]); j++) { 31 | if (node.text.toLowerCase().indexOf(token) == -1) { 32 | node.displayed = 0; 33 | } 34 | } 35 | } 36 | SelectBox.redisplay(id); 37 | }, 38 | delete_from_cache: function(id, value) { 39 | var node, delete_index = null; 40 | for (var i = 0; (node = SelectBox.cache[id][i]); i++) { 41 | if (node.value == value) { 42 | delete_index = i; 43 | break; 44 | } 45 | } 46 | var j = SelectBox.cache[id].length - 1; 47 | for (var i = delete_index; i < j; i++) { 48 | SelectBox.cache[id][i] = SelectBox.cache[id][i+1]; 49 | } 50 | SelectBox.cache[id].length--; 51 | }, 52 | add_to_cache: function(id, option) { 53 | SelectBox.cache[id].push({value: option.value, text: option.text, displayed: 1}); 54 | }, 55 | cache_contains: function(id, value) { 56 | // Check if an item is contained in the cache 57 | var node; 58 | for (var i = 0; (node = SelectBox.cache[id][i]); i++) { 59 | if (node.value == value) { 60 | return true; 61 | } 62 | } 63 | return false; 64 | }, 65 | move: function(from, to) { 66 | var from_box = document.getElementById(from); 67 | var to_box = document.getElementById(to); 68 | var option; 69 | for (var i = 0; (option = from_box.options[i]); i++) { 70 | if (option.selected && SelectBox.cache_contains(from, option.value)) { 71 | SelectBox.add_to_cache(to, {value: option.value, text: option.text, displayed: 1}); 72 | SelectBox.delete_from_cache(from, option.value); 73 | } 74 | } 75 | SelectBox.redisplay(from); 76 | SelectBox.redisplay(to); 77 | }, 78 | move_all: function(from, to) { 79 | var from_box = document.getElementById(from); 80 | var to_box = document.getElementById(to); 81 | var option; 82 | for (var i = 0; (option = from_box.options[i]); i++) { 83 | if (SelectBox.cache_contains(from, option.value)) { 84 | SelectBox.add_to_cache(to, {value: option.value, text: option.text, displayed: 1}); 85 | SelectBox.delete_from_cache(from, option.value); 86 | } 87 | } 88 | SelectBox.redisplay(from); 89 | SelectBox.redisplay(to); 90 | }, 91 | sort: function(id) { 92 | SelectBox.cache[id].sort( function(a, b) { 93 | a = a.text.toLowerCase(); 94 | b = b.text.toLowerCase(); 95 | try { 96 | if (a > b) return 1; 97 | if (a < b) return -1; 98 | } 99 | catch (e) { 100 | // silently fail on IE 'unknown' exception 101 | } 102 | return 0; 103 | } ); 104 | }, 105 | select_all: function(id) { 106 | var box = document.getElementById(id); 107 | for (var i = 0; i < box.options.length; i++) { 108 | box.options[i].selected = 'selected'; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /static/static_root/admin/js/SelectFilter2.js: -------------------------------------------------------------------------------- 1 | /* 2 | SelectFilter2 - Turns a multiple-select box into a filter interface. 3 | 4 | Requires core.js, SelectBox.js and addevent.js. 5 | */ 6 | (function($) { 7 | function findForm(node) { 8 | // returns the node of the form containing the given node 9 | if (node.tagName.toLowerCase() != 'form') { 10 | return findForm(node.parentNode); 11 | } 12 | return node; 13 | } 14 | 15 | window.SelectFilter = { 16 | init: function(field_id, field_name, is_stacked, admin_static_prefix) { 17 | if (field_id.match(/__prefix__/)){ 18 | // Don't intialize on empty forms. 19 | return; 20 | } 21 | var from_box = document.getElementById(field_id); 22 | from_box.id += '_from'; // change its ID 23 | from_box.className = 'filtered'; 24 | 25 | var ps = from_box.parentNode.getElementsByTagName('p'); 26 | for (var i=0; i, because it just gets in the way. 29 | from_box.parentNode.removeChild(ps[i]); 30 | } else if (ps[i].className.indexOf("help") != -1) { 31 | // Move help text up to the top so it isn't below the select 32 | // boxes or wrapped off on the side to the right of the add 33 | // button: 34 | from_box.parentNode.insertBefore(ps[i], from_box.parentNode.firstChild); 35 | } 36 | } 37 | 38 | //
or
39 | var selector_div = quickElement('div', from_box.parentNode); 40 | selector_div.className = is_stacked ? 'selector stacked' : 'selector'; 41 | 42 | //
43 | var selector_available = quickElement('div', selector_div, ''); 44 | selector_available.className = 'selector-available'; 45 | var title_available = quickElement('h2', selector_available, interpolate(gettext('Available %s') + ' ', [field_name])); 46 | quickElement('img', title_available, '', 'src', admin_static_prefix + 'img/icon-unknown.gif', 'width', '10', 'height', '10', 'class', 'help help-tooltip', 'title', interpolate(gettext('This is the list of available %s. You may choose some by selecting them in the box below and then clicking the "Choose" arrow between the two boxes.'), [field_name])); 47 | 48 | var filter_p = quickElement('p', selector_available, '', 'id', field_id + '_filter'); 49 | filter_p.className = 'selector-filter'; 50 | 51 | var search_filter_label = quickElement('label', filter_p, '', 'for', field_id + "_input"); 52 | 53 | var search_selector_img = quickElement('img', search_filter_label, '', 'src', admin_static_prefix + 'img/selector-search.gif', 'class', 'help-tooltip', 'alt', '', 'title', interpolate(gettext("Type into this box to filter down the list of available %s."), [field_name])); 54 | 55 | filter_p.appendChild(document.createTextNode(' ')); 56 | 57 | var filter_input = quickElement('input', filter_p, '', 'type', 'text', 'placeholder', gettext("Filter")); 58 | filter_input.id = field_id + '_input'; 59 | 60 | selector_available.appendChild(from_box); 61 | var choose_all = quickElement('a', selector_available, gettext('Choose all'), 'title', interpolate(gettext('Click to choose all %s at once.'), [field_name]), 'href', 'javascript: (function(){ SelectBox.move_all("' + field_id + '_from", "' + field_id + '_to"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_add_all_link'); 62 | choose_all.className = 'selector-chooseall'; 63 | 64 | //
    65 | var selector_chooser = quickElement('ul', selector_div, ''); 66 | selector_chooser.className = 'selector-chooser'; 67 | var add_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Choose'), 'title', gettext('Choose'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_from","' + field_id + '_to"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_add_link'); 68 | add_link.className = 'selector-add'; 69 | var remove_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Remove'), 'title', gettext('Remove'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_to","' + field_id + '_from"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_remove_link'); 70 | remove_link.className = 'selector-remove'; 71 | 72 | //
    73 | var selector_chosen = quickElement('div', selector_div, ''); 74 | selector_chosen.className = 'selector-chosen'; 75 | var title_chosen = quickElement('h2', selector_chosen, interpolate(gettext('Chosen %s') + ' ', [field_name])); 76 | quickElement('img', title_chosen, '', 'src', admin_static_prefix + 'img/icon-unknown.gif', 'width', '10', 'height', '10', 'class', 'help help-tooltip', 'title', interpolate(gettext('This is the list of chosen %s. You may remove some by selecting them in the box below and then clicking the "Remove" arrow between the two boxes.'), [field_name])); 77 | 78 | var to_box = quickElement('select', selector_chosen, '', 'id', field_id + '_to', 'multiple', 'multiple', 'size', from_box.size, 'name', from_box.getAttribute('name')); 79 | to_box.className = 'filtered'; 80 | var clear_all = quickElement('a', selector_chosen, gettext('Remove all'), 'title', interpolate(gettext('Click to remove all chosen %s at once.'), [field_name]), 'href', 'javascript: (function() { SelectBox.move_all("' + field_id + '_to", "' + field_id + '_from"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_remove_all_link'); 81 | clear_all.className = 'selector-clearall'; 82 | 83 | from_box.setAttribute('name', from_box.getAttribute('name') + '_old'); 84 | 85 | // Set up the JavaScript event handlers for the select box filter interface 86 | addEvent(filter_input, 'keyup', function(e) { SelectFilter.filter_key_up(e, field_id); }); 87 | addEvent(filter_input, 'keydown', function(e) { SelectFilter.filter_key_down(e, field_id); }); 88 | addEvent(from_box, 'change', function(e) { SelectFilter.refresh_icons(field_id) }); 89 | addEvent(to_box, 'change', function(e) { SelectFilter.refresh_icons(field_id) }); 90 | addEvent(from_box, 'dblclick', function() { SelectBox.move(field_id + '_from', field_id + '_to'); SelectFilter.refresh_icons(field_id); }); 91 | addEvent(to_box, 'dblclick', function() { SelectBox.move(field_id + '_to', field_id + '_from'); SelectFilter.refresh_icons(field_id); }); 92 | addEvent(findForm(from_box), 'submit', function() { SelectBox.select_all(field_id + '_to'); }); 93 | SelectBox.init(field_id + '_from'); 94 | SelectBox.init(field_id + '_to'); 95 | // Move selected from_box options to to_box 96 | SelectBox.move(field_id + '_from', field_id + '_to'); 97 | 98 | if (!is_stacked) { 99 | // In horizontal mode, give the same height to the two boxes. 100 | var j_from_box = $(from_box); 101 | var j_to_box = $(to_box); 102 | var resize_filters = function() { j_to_box.height($(filter_p).outerHeight() + j_from_box.outerHeight()); } 103 | if (j_from_box.outerHeight() > 0) { 104 | resize_filters(); // This fieldset is already open. Resize now. 105 | } else { 106 | // This fieldset is probably collapsed. Wait for its 'show' event. 107 | j_to_box.closest('fieldset').one('show.fieldset', resize_filters); 108 | } 109 | } 110 | 111 | // Initial icon refresh 112 | SelectFilter.refresh_icons(field_id); 113 | }, 114 | refresh_icons: function(field_id) { 115 | var from = $('#' + field_id + '_from'); 116 | var to = $('#' + field_id + '_to'); 117 | var is_from_selected = from.find('option:selected').length > 0; 118 | var is_to_selected = to.find('option:selected').length > 0; 119 | // Active if at least one item is selected 120 | $('#' + field_id + '_add_link').toggleClass('active', is_from_selected); 121 | $('#' + field_id + '_remove_link').toggleClass('active', is_to_selected); 122 | // Active if the corresponding box isn't empty 123 | $('#' + field_id + '_add_all_link').toggleClass('active', from.find('option').length > 0); 124 | $('#' + field_id + '_remove_all_link').toggleClass('active', to.find('option').length > 0); 125 | }, 126 | filter_key_up: function(event, field_id) { 127 | var from = document.getElementById(field_id + '_from'); 128 | // don't submit form if user pressed Enter 129 | if ((event.which && event.which == 13) || (event.keyCode && event.keyCode == 13)) { 130 | from.selectedIndex = 0; 131 | SelectBox.move(field_id + '_from', field_id + '_to'); 132 | from.selectedIndex = 0; 133 | return false; 134 | } 135 | var temp = from.selectedIndex; 136 | SelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value); 137 | from.selectedIndex = temp; 138 | return true; 139 | }, 140 | filter_key_down: function(event, field_id) { 141 | var from = document.getElementById(field_id + '_from'); 142 | // right arrow -- move across 143 | if ((event.which && event.which == 39) || (event.keyCode && event.keyCode == 39)) { 144 | var old_index = from.selectedIndex; 145 | SelectBox.move(field_id + '_from', field_id + '_to'); 146 | from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index; 147 | return false; 148 | } 149 | // down arrow -- wrap around 150 | if ((event.which && event.which == 40) || (event.keyCode && event.keyCode == 40)) { 151 | from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1; 152 | } 153 | // up arrow -- wrap around 154 | if ((event.which && event.which == 38) || (event.keyCode && event.keyCode == 38)) { 155 | from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1; 156 | } 157 | return true; 158 | } 159 | } 160 | 161 | })(django.jQuery); 162 | -------------------------------------------------------------------------------- /static/static_root/admin/js/actions.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $.fn.actions = function(opts) { 3 | var options = $.extend({}, $.fn.actions.defaults, opts); 4 | var actionCheckboxes = $(this); 5 | var list_editable_changed = false; 6 | var checker = function(checked) { 7 | if (checked) { 8 | showQuestion(); 9 | } else { 10 | reset(); 11 | } 12 | $(actionCheckboxes).prop("checked", checked) 13 | .parent().parent().toggleClass(options.selectedClass, checked); 14 | }, 15 | updateCounter = function() { 16 | var sel = $(actionCheckboxes).filter(":checked").length; 17 | $(options.counterContainer).html(interpolate( 18 | ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), { 19 | sel: sel, 20 | cnt: _actions_icnt 21 | }, true)); 22 | $(options.allToggle).prop("checked", function() { 23 | if (sel == actionCheckboxes.length) { 24 | value = true; 25 | showQuestion(); 26 | } else { 27 | value = false; 28 | clearAcross(); 29 | } 30 | return value; 31 | }); 32 | }, 33 | showQuestion = function() { 34 | $(options.acrossClears).hide(); 35 | $(options.acrossQuestions).show(); 36 | $(options.allContainer).hide(); 37 | }, 38 | showClear = function() { 39 | $(options.acrossClears).show(); 40 | $(options.acrossQuestions).hide(); 41 | $(options.actionContainer).toggleClass(options.selectedClass); 42 | $(options.allContainer).show(); 43 | $(options.counterContainer).hide(); 44 | }, 45 | reset = function() { 46 | $(options.acrossClears).hide(); 47 | $(options.acrossQuestions).hide(); 48 | $(options.allContainer).hide(); 49 | $(options.counterContainer).show(); 50 | }, 51 | clearAcross = function() { 52 | reset(); 53 | $(options.acrossInput).val(0); 54 | $(options.actionContainer).removeClass(options.selectedClass); 55 | }; 56 | // Show counter by default 57 | $(options.counterContainer).show(); 58 | // Check state of checkboxes and reinit state if needed 59 | $(this).filter(":checked").each(function(i) { 60 | $(this).parent().parent().toggleClass(options.selectedClass); 61 | updateCounter(); 62 | if ($(options.acrossInput).val() == 1) { 63 | showClear(); 64 | } 65 | }); 66 | $(options.allToggle).show().click(function() { 67 | checker($(this).prop("checked")); 68 | updateCounter(); 69 | }); 70 | $("div.actions span.question a").click(function(event) { 71 | event.preventDefault(); 72 | $(options.acrossInput).val(1); 73 | showClear(); 74 | }); 75 | $("div.actions span.clear a").click(function(event) { 76 | event.preventDefault(); 77 | $(options.allToggle).prop("checked", false); 78 | clearAcross(); 79 | checker(0); 80 | updateCounter(); 81 | }); 82 | lastChecked = null; 83 | $(actionCheckboxes).click(function(event) { 84 | if (!event) { event = window.event; } 85 | var target = event.target ? event.target : event.srcElement; 86 | if (lastChecked && $.data(lastChecked) != $.data(target) && event.shiftKey === true) { 87 | var inrange = false; 88 | $(lastChecked).prop("checked", target.checked) 89 | .parent().parent().toggleClass(options.selectedClass, target.checked); 90 | $(actionCheckboxes).each(function() { 91 | if ($.data(this) == $.data(lastChecked) || $.data(this) == $.data(target)) { 92 | inrange = (inrange) ? false : true; 93 | } 94 | if (inrange) { 95 | $(this).prop("checked", target.checked) 96 | .parent().parent().toggleClass(options.selectedClass, target.checked); 97 | } 98 | }); 99 | } 100 | $(target).parent().parent().toggleClass(options.selectedClass, target.checked); 101 | lastChecked = target; 102 | updateCounter(); 103 | }); 104 | $('form#changelist-form table#result_list tr').find('td:gt(0) :input').change(function() { 105 | list_editable_changed = true; 106 | }); 107 | $('form#changelist-form button[name="index"]').click(function(event) { 108 | if (list_editable_changed) { 109 | return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost.")); 110 | } 111 | }); 112 | $('form#changelist-form input[name="_save"]').click(function(event) { 113 | var action_changed = false; 114 | $('div.actions select option:selected').each(function() { 115 | if ($(this).val()) { 116 | action_changed = true; 117 | } 118 | }); 119 | if (action_changed) { 120 | if (list_editable_changed) { 121 | return confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")); 122 | } else { 123 | return confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button.")); 124 | } 125 | } 126 | }); 127 | }; 128 | /* Setup plugin defaults */ 129 | $.fn.actions.defaults = { 130 | actionContainer: "div.actions", 131 | counterContainer: "span.action-counter", 132 | allContainer: "div.actions span.all", 133 | acrossInput: "div.actions input.select-across", 134 | acrossQuestions: "div.actions span.question", 135 | acrossClears: "div.actions span.clear", 136 | allToggle: "#action-toggle", 137 | selectedClass: "selected" 138 | }; 139 | })(django.jQuery); 140 | -------------------------------------------------------------------------------- /static/static_root/admin/js/actions.min.js: -------------------------------------------------------------------------------- 1 | (function(a){a.fn.actions=function(n){var b=a.extend({},a.fn.actions.defaults,n),e=a(this),g=false,k=function(c){c?i():j();a(e).prop("checked",c).parent().parent().toggleClass(b.selectedClass,c)},f=function(){var c=a(e).filter(":checked").length;a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:_actions_icnt},true));a(b.allToggle).prop("checked",function(){if(c==e.length){value=true;i()}else{value=false;l()}return value})},i= 2 | function(){a(b.acrossClears).hide();a(b.acrossQuestions).show();a(b.allContainer).hide()},m=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()},j=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()},l=function(){j();a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)};a(b.counterContainer).show(); 3 | a(this).filter(":checked").each(function(){a(this).parent().parent().toggleClass(b.selectedClass);f();a(b.acrossInput).val()==1&&m()});a(b.allToggle).show().click(function(){k(a(this).prop("checked"));f()});a("div.actions span.question a").click(function(c){c.preventDefault();a(b.acrossInput).val(1);m()});a("div.actions span.clear a").click(function(c){c.preventDefault();a(b.allToggle).prop("checked",false);l();k(0);f()});lastChecked=null;a(e).click(function(c){if(!c)c=window.event;var d=c.target? 4 | c.target:c.srcElement;if(lastChecked&&a.data(lastChecked)!=a.data(d)&&c.shiftKey===true){var h=false;a(lastChecked).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked);a(e).each(function(){if(a.data(this)==a.data(lastChecked)||a.data(this)==a.data(d))h=h?false:true;h&&a(this).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);lastChecked=d;f()});a("form#changelist-form table#result_list tr").find("td:gt(0) :input").change(function(){g= 5 | true});a('form#changelist-form button[name="index"]').click(function(){if(g)return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."))});a('form#changelist-form input[name="_save"]').click(function(){var c=false;a("div.actions select option:selected").each(function(){if(a(this).val())c=true});if(c)return g?confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")): 6 | confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button."))})};a.fn.actions.defaults={actionContainer:"div.actions",counterContainer:"span.action-counter",allContainer:"div.actions span.all",acrossInput:"div.actions input.select-across",acrossQuestions:"div.actions span.question",acrossClears:"div.actions span.clear",allToggle:"#action-toggle",selectedClass:"selected"}})(django.jQuery); 7 | -------------------------------------------------------------------------------- /static/static_root/admin/js/admin/RelatedObjectLookups.js: -------------------------------------------------------------------------------- 1 | // Handles related-objects functionality: lookup link for raw_id_fields 2 | // and Add Another links. 3 | 4 | function html_unescape(text) { 5 | // Unescape a string that was escaped using django.utils.html.escape. 6 | text = text.replace(/</g, '<'); 7 | text = text.replace(/>/g, '>'); 8 | text = text.replace(/"/g, '"'); 9 | text = text.replace(/'/g, "'"); 10 | text = text.replace(/&/g, '&'); 11 | return text; 12 | } 13 | 14 | // IE doesn't accept periods or dashes in the window name, but the element IDs 15 | // we use to generate popup window names may contain them, therefore we map them 16 | // to allowed characters in a reversible way so that we can locate the correct 17 | // element when the popup window is dismissed. 18 | function id_to_windowname(text) { 19 | text = text.replace(/\./g, '__dot__'); 20 | text = text.replace(/\-/g, '__dash__'); 21 | return text; 22 | } 23 | 24 | function windowname_to_id(text) { 25 | text = text.replace(/__dot__/g, '.'); 26 | text = text.replace(/__dash__/g, '-'); 27 | return text; 28 | } 29 | 30 | function showRelatedObjectLookupPopup(triggeringLink) { 31 | var name = triggeringLink.id.replace(/^lookup_/, ''); 32 | name = id_to_windowname(name); 33 | var href; 34 | if (triggeringLink.href.search(/\?/) >= 0) { 35 | href = triggeringLink.href + '&_popup=1'; 36 | } else { 37 | href = triggeringLink.href + '?_popup=1'; 38 | } 39 | var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); 40 | win.focus(); 41 | return false; 42 | } 43 | 44 | function dismissRelatedLookupPopup(win, chosenId) { 45 | var name = windowname_to_id(win.name); 46 | var elem = document.getElementById(name); 47 | if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) { 48 | elem.value += ',' + chosenId; 49 | } else { 50 | document.getElementById(name).value = chosenId; 51 | } 52 | win.close(); 53 | } 54 | 55 | function showAddAnotherPopup(triggeringLink) { 56 | var name = triggeringLink.id.replace(/^add_/, ''); 57 | name = id_to_windowname(name); 58 | href = triggeringLink.href 59 | if (href.indexOf('?') == -1) { 60 | href += '?_popup=1'; 61 | } else { 62 | href += '&_popup=1'; 63 | } 64 | var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); 65 | win.focus(); 66 | return false; 67 | } 68 | 69 | function dismissAddAnotherPopup(win, newId, newRepr) { 70 | // newId and newRepr are expected to have previously been escaped by 71 | // django.utils.html.escape. 72 | newId = html_unescape(newId); 73 | newRepr = html_unescape(newRepr); 74 | var name = windowname_to_id(win.name); 75 | var elem = document.getElementById(name); 76 | if (elem) { 77 | var elemName = elem.nodeName.toUpperCase(); 78 | if (elemName == 'SELECT') { 79 | var o = new Option(newRepr, newId); 80 | elem.options[elem.options.length] = o; 81 | o.selected = true; 82 | } else if (elemName == 'INPUT') { 83 | if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) { 84 | elem.value += ',' + newId; 85 | } else { 86 | elem.value = newId; 87 | } 88 | } 89 | } else { 90 | var toId = name + "_to"; 91 | elem = document.getElementById(toId); 92 | var o = new Option(newRepr, newId); 93 | SelectBox.add_to_cache(toId, o); 94 | SelectBox.redisplay(toId); 95 | } 96 | win.close(); 97 | } 98 | -------------------------------------------------------------------------------- /static/static_root/admin/js/calendar.js: -------------------------------------------------------------------------------- 1 | /* 2 | calendar.js - Calendar functions by Adrian Holovaty 3 | depends on core.js for utility functions like removeChildren or quickElement 4 | */ 5 | 6 | // CalendarNamespace -- Provides a collection of HTML calendar-related helper functions 7 | var CalendarNamespace = { 8 | monthsOfYear: gettext('January February March April May June July August September October November December').split(' '), 9 | daysOfWeek: gettext('S M T W T F S').split(' '), 10 | firstDayOfWeek: parseInt(get_format('FIRST_DAY_OF_WEEK')), 11 | isLeapYear: function(year) { 12 | return (((year % 4)==0) && ((year % 100)!=0) || ((year % 400)==0)); 13 | }, 14 | getDaysInMonth: function(month,year) { 15 | var days; 16 | if (month==1 || month==3 || month==5 || month==7 || month==8 || month==10 || month==12) { 17 | days = 31; 18 | } 19 | else if (month==4 || month==6 || month==9 || month==11) { 20 | days = 30; 21 | } 22 | else if (month==2 && CalendarNamespace.isLeapYear(year)) { 23 | days = 29; 24 | } 25 | else { 26 | days = 28; 27 | } 28 | return days; 29 | }, 30 | draw: function(month, year, div_id, callback) { // month = 1-12, year = 1-9999 31 | var today = new Date(); 32 | var todayDay = today.getDate(); 33 | var todayMonth = today.getMonth()+1; 34 | var todayYear = today.getFullYear(); 35 | var todayClass = ''; 36 | 37 | month = parseInt(month); 38 | year = parseInt(year); 39 | var calDiv = document.getElementById(div_id); 40 | removeChildren(calDiv); 41 | var calTable = document.createElement('table'); 42 | quickElement('caption', calTable, CalendarNamespace.monthsOfYear[month-1] + ' ' + year); 43 | var tableBody = quickElement('tbody', calTable); 44 | 45 | // Draw days-of-week header 46 | var tableRow = quickElement('tr', tableBody); 47 | for (var i = 0; i < 7; i++) { 48 | quickElement('th', tableRow, CalendarNamespace.daysOfWeek[(i + CalendarNamespace.firstDayOfWeek) % 7]); 49 | } 50 | 51 | var startingPos = new Date(year, month-1, 1 - CalendarNamespace.firstDayOfWeek).getDay(); 52 | var days = CalendarNamespace.getDaysInMonth(month, year); 53 | 54 | // Draw blanks before first of month 55 | tableRow = quickElement('tr', tableBody); 56 | for (var i = 0; i < startingPos; i++) { 57 | var _cell = quickElement('td', tableRow, ' '); 58 | _cell.style.backgroundColor = '#f3f3f3'; 59 | } 60 | 61 | // Draw days of month 62 | var currentDay = 1; 63 | for (var i = startingPos; currentDay <= days; i++) { 64 | if (i%7 == 0 && currentDay != 1) { 65 | tableRow = quickElement('tr', tableBody); 66 | } 67 | if ((currentDay==todayDay) && (month==todayMonth) && (year==todayYear)) { 68 | todayClass='today'; 69 | } else { 70 | todayClass=''; 71 | } 72 | var cell = quickElement('td', tableRow, '', 'class', todayClass); 73 | 74 | quickElement('a', cell, currentDay, 'href', 'javascript:void(' + callback + '('+year+','+month+','+currentDay+'));'); 75 | currentDay++; 76 | } 77 | 78 | // Draw blanks after end of month (optional, but makes for valid code) 79 | while (tableRow.childNodes.length < 7) { 80 | var _cell = quickElement('td', tableRow, ' '); 81 | _cell.style.backgroundColor = '#f3f3f3'; 82 | } 83 | 84 | calDiv.appendChild(calTable); 85 | } 86 | } 87 | 88 | // Calendar -- A calendar instance 89 | function Calendar(div_id, callback) { 90 | // div_id (string) is the ID of the element in which the calendar will 91 | // be displayed 92 | // callback (string) is the name of a JavaScript function that will be 93 | // called with the parameters (year, month, day) when a day in the 94 | // calendar is clicked 95 | this.div_id = div_id; 96 | this.callback = callback; 97 | this.today = new Date(); 98 | this.currentMonth = this.today.getMonth() + 1; 99 | this.currentYear = this.today.getFullYear(); 100 | } 101 | Calendar.prototype = { 102 | drawCurrent: function() { 103 | CalendarNamespace.draw(this.currentMonth, this.currentYear, this.div_id, this.callback); 104 | }, 105 | drawDate: function(month, year) { 106 | this.currentMonth = month; 107 | this.currentYear = year; 108 | this.drawCurrent(); 109 | }, 110 | drawPreviousMonth: function() { 111 | if (this.currentMonth == 1) { 112 | this.currentMonth = 12; 113 | this.currentYear--; 114 | } 115 | else { 116 | this.currentMonth--; 117 | } 118 | this.drawCurrent(); 119 | }, 120 | drawNextMonth: function() { 121 | if (this.currentMonth == 12) { 122 | this.currentMonth = 1; 123 | this.currentYear++; 124 | } 125 | else { 126 | this.currentMonth++; 127 | } 128 | this.drawCurrent(); 129 | }, 130 | drawPreviousYear: function() { 131 | this.currentYear--; 132 | this.drawCurrent(); 133 | }, 134 | drawNextYear: function() { 135 | this.currentYear++; 136 | this.drawCurrent(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /static/static_root/admin/js/collapse.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $(document).ready(function() { 3 | // Add anchor tag for Show/Hide link 4 | $("fieldset.collapse").each(function(i, elem) { 5 | // Don't hide if fields in this fieldset have errors 6 | if ($(elem).find("div.errors").length == 0) { 7 | $(elem).addClass("collapsed").find("h2").first().append(' (' + gettext("Show") + 9 | ')'); 10 | } 11 | }); 12 | // Add toggle to anchor tag 13 | $("fieldset.collapse a.collapse-toggle").click(function(ev) { 14 | if ($(this).closest("fieldset").hasClass("collapsed")) { 15 | // Show 16 | $(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset", [$(this).attr("id")]); 17 | } else { 18 | // Hide 19 | $(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", [$(this).attr("id")]); 20 | } 21 | return false; 22 | }); 23 | }); 24 | })(django.jQuery); 25 | -------------------------------------------------------------------------------- /static/static_root/admin/js/collapse.min.js: -------------------------------------------------------------------------------- 1 | (function(a){a(document).ready(function(){a("fieldset.collapse").each(function(c,b){0==a(b).find("div.errors").length&&a(b).addClass("collapsed").find("h2").first().append(' ('+gettext("Show")+")")});a("fieldset.collapse a.collapse-toggle").click(function(){a(this).closest("fieldset").hasClass("collapsed")?a(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset",[a(this).attr("id")]):a(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", 2 | [a(this).attr("id")]);return!1})})})(django.jQuery); 3 | -------------------------------------------------------------------------------- /static/static_root/admin/js/core.js: -------------------------------------------------------------------------------- 1 | // Core javascript helper functions 2 | 3 | // basic browser identification & version 4 | var isOpera = (navigator.userAgent.indexOf("Opera")>=0) && parseFloat(navigator.appVersion); 5 | var isIE = ((document.all) && (!isOpera)) && parseFloat(navigator.appVersion.split("MSIE ")[1].split(";")[0]); 6 | 7 | // Cross-browser event handlers. 8 | function addEvent(obj, evType, fn) { 9 | if (obj.addEventListener) { 10 | obj.addEventListener(evType, fn, false); 11 | return true; 12 | } else if (obj.attachEvent) { 13 | var r = obj.attachEvent("on" + evType, fn); 14 | return r; 15 | } else { 16 | return false; 17 | } 18 | } 19 | 20 | function removeEvent(obj, evType, fn) { 21 | if (obj.removeEventListener) { 22 | obj.removeEventListener(evType, fn, false); 23 | return true; 24 | } else if (obj.detachEvent) { 25 | obj.detachEvent("on" + evType, fn); 26 | return true; 27 | } else { 28 | return false; 29 | } 30 | } 31 | 32 | function cancelEventPropagation(e) { 33 | if (!e) e = window.event; 34 | e.cancelBubble = true; 35 | if (e.stopPropagation) e.stopPropagation(); 36 | } 37 | 38 | // quickElement(tagType, parentReference, textInChildNode, [, attribute, attributeValue ...]); 39 | function quickElement() { 40 | var obj = document.createElement(arguments[0]); 41 | if (arguments[2] != '' && arguments[2] != null) { 42 | var textNode = document.createTextNode(arguments[2]); 43 | obj.appendChild(textNode); 44 | } 45 | var len = arguments.length; 46 | for (var i = 3; i < len; i += 2) { 47 | obj.setAttribute(arguments[i], arguments[i+1]); 48 | } 49 | arguments[1].appendChild(obj); 50 | return obj; 51 | } 52 | 53 | // "a" is reference to an object 54 | function removeChildren(a) { 55 | while (a.hasChildNodes()) a.removeChild(a.lastChild); 56 | } 57 | 58 | // ---------------------------------------------------------------------------- 59 | // Cross-browser xmlhttp object 60 | // from http://jibbering.com/2002/4/httprequest.html 61 | // ---------------------------------------------------------------------------- 62 | var xmlhttp; 63 | /*@cc_on @*/ 64 | /*@if (@_jscript_version >= 5) 65 | try { 66 | xmlhttp = new ActiveXObject("Msxml2.XMLHTTP"); 67 | } catch (e) { 68 | try { 69 | xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); 70 | } catch (E) { 71 | xmlhttp = false; 72 | } 73 | } 74 | @else 75 | xmlhttp = false; 76 | @end @*/ 77 | if (!xmlhttp && typeof XMLHttpRequest != 'undefined') { 78 | xmlhttp = new XMLHttpRequest(); 79 | } 80 | 81 | // ---------------------------------------------------------------------------- 82 | // Find-position functions by PPK 83 | // See http://www.quirksmode.org/js/findpos.html 84 | // ---------------------------------------------------------------------------- 85 | function findPosX(obj) { 86 | var curleft = 0; 87 | if (obj.offsetParent) { 88 | while (obj.offsetParent) { 89 | curleft += obj.offsetLeft - ((isOpera) ? 0 : obj.scrollLeft); 90 | obj = obj.offsetParent; 91 | } 92 | // IE offsetParent does not include the top-level 93 | if (isIE && obj.parentElement){ 94 | curleft += obj.offsetLeft - obj.scrollLeft; 95 | } 96 | } else if (obj.x) { 97 | curleft += obj.x; 98 | } 99 | return curleft; 100 | } 101 | 102 | function findPosY(obj) { 103 | var curtop = 0; 104 | if (obj.offsetParent) { 105 | while (obj.offsetParent) { 106 | curtop += obj.offsetTop - ((isOpera) ? 0 : obj.scrollTop); 107 | obj = obj.offsetParent; 108 | } 109 | // IE offsetParent does not include the top-level 110 | if (isIE && obj.parentElement){ 111 | curtop += obj.offsetTop - obj.scrollTop; 112 | } 113 | } else if (obj.y) { 114 | curtop += obj.y; 115 | } 116 | return curtop; 117 | } 118 | 119 | //----------------------------------------------------------------------------- 120 | // Date object extensions 121 | // ---------------------------------------------------------------------------- 122 | 123 | Date.prototype.getTwelveHours = function() { 124 | hours = this.getHours(); 125 | if (hours == 0) { 126 | return 12; 127 | } 128 | else { 129 | return hours <= 12 ? hours : hours-12 130 | } 131 | } 132 | 133 | Date.prototype.getTwoDigitMonth = function() { 134 | return (this.getMonth() < 9) ? '0' + (this.getMonth()+1) : (this.getMonth()+1); 135 | } 136 | 137 | Date.prototype.getTwoDigitDate = function() { 138 | return (this.getDate() < 10) ? '0' + this.getDate() : this.getDate(); 139 | } 140 | 141 | Date.prototype.getTwoDigitTwelveHour = function() { 142 | return (this.getTwelveHours() < 10) ? '0' + this.getTwelveHours() : this.getTwelveHours(); 143 | } 144 | 145 | Date.prototype.getTwoDigitHour = function() { 146 | return (this.getHours() < 10) ? '0' + this.getHours() : this.getHours(); 147 | } 148 | 149 | Date.prototype.getTwoDigitMinute = function() { 150 | return (this.getMinutes() < 10) ? '0' + this.getMinutes() : this.getMinutes(); 151 | } 152 | 153 | Date.prototype.getTwoDigitSecond = function() { 154 | return (this.getSeconds() < 10) ? '0' + this.getSeconds() : this.getSeconds(); 155 | } 156 | 157 | Date.prototype.getHourMinute = function() { 158 | return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute(); 159 | } 160 | 161 | Date.prototype.getHourMinuteSecond = function() { 162 | return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute() + ':' + this.getTwoDigitSecond(); 163 | } 164 | 165 | Date.prototype.strftime = function(format) { 166 | var fields = { 167 | c: this.toString(), 168 | d: this.getTwoDigitDate(), 169 | H: this.getTwoDigitHour(), 170 | I: this.getTwoDigitTwelveHour(), 171 | m: this.getTwoDigitMonth(), 172 | M: this.getTwoDigitMinute(), 173 | p: (this.getHours() >= 12) ? 'PM' : 'AM', 174 | S: this.getTwoDigitSecond(), 175 | w: '0' + this.getDay(), 176 | x: this.toLocaleDateString(), 177 | X: this.toLocaleTimeString(), 178 | y: ('' + this.getFullYear()).substr(2, 4), 179 | Y: '' + this.getFullYear(), 180 | '%' : '%' 181 | }; 182 | var result = '', i = 0; 183 | while (i < format.length) { 184 | if (format.charAt(i) === '%') { 185 | result = result + fields[format.charAt(i + 1)]; 186 | ++i; 187 | } 188 | else { 189 | result = result + format.charAt(i); 190 | } 191 | ++i; 192 | } 193 | return result; 194 | } 195 | 196 | // ---------------------------------------------------------------------------- 197 | // String object extensions 198 | // ---------------------------------------------------------------------------- 199 | String.prototype.pad_left = function(pad_length, pad_string) { 200 | var new_string = this; 201 | for (var i = 0; new_string.length < pad_length; i++) { 202 | new_string = pad_string + new_string; 203 | } 204 | return new_string; 205 | } 206 | 207 | // ---------------------------------------------------------------------------- 208 | // Get the computed style for and element 209 | // ---------------------------------------------------------------------------- 210 | function getStyle(oElm, strCssRule){ 211 | var strValue = ""; 212 | if(document.defaultView && document.defaultView.getComputedStyle){ 213 | strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule); 214 | } 215 | else if(oElm.currentStyle){ 216 | strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){ 217 | return p1.toUpperCase(); 218 | }); 219 | strValue = oElm.currentStyle[strCssRule]; 220 | } 221 | return strValue; 222 | } 223 | -------------------------------------------------------------------------------- /static/static_root/admin/js/inlines.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Django admin inlines 3 | * 4 | * Based on jQuery Formset 1.1 5 | * @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com) 6 | * @requires jQuery 1.2.6 or later 7 | * 8 | * Copyright (c) 2009, Stanislaus Madueke 9 | * All rights reserved. 10 | * 11 | * Spiced up with Code from Zain Memon's GSoC project 2009 12 | * and modified for Django by Jannis Leidel, Travis Swicegood and Julien Phalip. 13 | * 14 | * Licensed under the New BSD License 15 | * See: http://www.opensource.org/licenses/bsd-license.php 16 | */ 17 | (function($) { 18 | $.fn.formset = function(opts) { 19 | var options = $.extend({}, $.fn.formset.defaults, opts); 20 | var $this = $(this); 21 | var $parent = $this.parent(); 22 | var updateElementIndex = function(el, prefix, ndx) { 23 | var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))"); 24 | var replacement = prefix + "-" + ndx; 25 | if ($(el).prop("for")) { 26 | $(el).prop("for", $(el).prop("for").replace(id_regex, replacement)); 27 | } 28 | if (el.id) { 29 | el.id = el.id.replace(id_regex, replacement); 30 | } 31 | if (el.name) { 32 | el.name = el.name.replace(id_regex, replacement); 33 | } 34 | }; 35 | var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").prop("autocomplete", "off"); 36 | var nextIndex = parseInt(totalForms.val(), 10); 37 | var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").prop("autocomplete", "off"); 38 | // only show the add button if we are allowed to add more items, 39 | // note that max_num = None translates to a blank string. 40 | var showAddButton = maxForms.val() === '' || (maxForms.val()-totalForms.val()) > 0; 41 | $this.each(function(i) { 42 | $(this).not("." + options.emptyCssClass).addClass(options.formCssClass); 43 | }); 44 | if ($this.length && showAddButton) { 45 | var addButton; 46 | if ($this.prop("tagName") == "TR") { 47 | // If forms are laid out as table rows, insert the 48 | // "add" button in a new table row: 49 | var numCols = this.eq(-1).children().length; 50 | $parent.append('' + options.addText + ""); 51 | addButton = $parent.find("tr:last a"); 52 | } else { 53 | // Otherwise, insert it immediately after the last form: 54 | $this.filter(":last").after('"); 55 | addButton = $this.filter(":last").next().find("a"); 56 | } 57 | addButton.click(function(e) { 58 | e.preventDefault(); 59 | var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS"); 60 | var template = $("#" + options.prefix + "-empty"); 61 | var row = template.clone(true); 62 | row.removeClass(options.emptyCssClass) 63 | .addClass(options.formCssClass) 64 | .attr("id", options.prefix + "-" + nextIndex); 65 | if (row.is("tr")) { 66 | // If the forms are laid out in table rows, insert 67 | // the remove button into the last table cell: 68 | row.children(":last").append('"); 69 | } else if (row.is("ul") || row.is("ol")) { 70 | // If they're laid out as an ordered/unordered list, 71 | // insert an
  • after the last list item: 72 | row.append('
  • ' + options.deleteText + "
  • "); 73 | } else { 74 | // Otherwise, just insert the remove button as the 75 | // last child element of the form's container: 76 | row.children(":first").append('' + options.deleteText + ""); 77 | } 78 | row.find("*").each(function() { 79 | updateElementIndex(this, options.prefix, totalForms.val()); 80 | }); 81 | // Insert the new form when it has been fully edited 82 | row.insertBefore($(template)); 83 | // Update number of total forms 84 | $(totalForms).val(parseInt(totalForms.val(), 10) + 1); 85 | nextIndex += 1; 86 | // Hide add button in case we've hit the max, except we want to add infinitely 87 | if ((maxForms.val() !== '') && (maxForms.val()-totalForms.val()) <= 0) { 88 | addButton.parent().hide(); 89 | } 90 | // The delete button of each row triggers a bunch of other things 91 | row.find("a." + options.deleteCssClass).click(function(e) { 92 | e.preventDefault(); 93 | // Remove the parent form containing this button: 94 | var row = $(this).parents("." + options.formCssClass); 95 | row.remove(); 96 | nextIndex -= 1; 97 | // If a post-delete callback was provided, call it with the deleted form: 98 | if (options.removed) { 99 | options.removed(row); 100 | } 101 | // Update the TOTAL_FORMS form count. 102 | var forms = $("." + options.formCssClass); 103 | $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length); 104 | // Show add button again once we drop below max 105 | if ((maxForms.val() === '') || (maxForms.val()-forms.length) > 0) { 106 | addButton.parent().show(); 107 | } 108 | // Also, update names and ids for all remaining form controls 109 | // so they remain in sequence: 110 | for (var i=0, formCount=forms.length; i'+a.addText+""),h=d.find("tr:last a")):(c.filter(":last").after('"),h=c.filter(":last").next().find("a"));h.click(function(d){d.preventDefault();var f=b("#id_"+a.prefix+"-TOTAL_FORMS"),d=b("#"+a.prefix+ 3 | "-empty"),c=d.clone(true);c.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+g);c.is("tr")?c.children(":last").append('"):c.is("ul")||c.is("ol")?c.append('
  • '+a.deleteText+"
  • "):c.children(":first").append(''+a.deleteText+"");c.find("*").each(function(){i(this, 4 | a.prefix,f.val())});c.insertBefore(b(d));b(f).val(parseInt(f.val(),10)+1);g=g+1;e.val()!==""&&e.val()-f.val()<=0&&h.parent().hide();c.find("a."+a.deleteCssClass).click(function(d){d.preventDefault();d=b(this).parents("."+a.formCssClass);d.remove();g=g-1;a.removed&&a.removed(d);d=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(d.length);(e.val()===""||e.val()-d.length>0)&&h.parent().show();for(var c=0,f=d.length;c 0) { 25 | values.push($(field).val()); 26 | } 27 | }) 28 | field.val(URLify(values.join(' '), maxLength)); 29 | }; 30 | 31 | $(dependencies.join(',')).keyup(populate).change(populate).focus(populate); 32 | }); 33 | }; 34 | })(django.jQuery); 35 | -------------------------------------------------------------------------------- /static/static_root/admin/js/prepopulate.min.js: -------------------------------------------------------------------------------- 1 | (function(a){a.fn.prepopulate=function(d,g){return this.each(function(){var b=a(this);b.data("_changed",false);b.change(function(){b.data("_changed",true)});var c=function(){if(b.data("_changed")!=true){var e=[];a.each(d,function(h,f){a(f).val().length>0&&e.push(a(f).val())});b.val(URLify(e.join(" "),g))}};a(d.join(",")).keyup(c).change(c).focus(c)})}})(django.jQuery); 2 | -------------------------------------------------------------------------------- /static/static_root/admin/js/timeparse.js: -------------------------------------------------------------------------------- 1 | var timeParsePatterns = [ 2 | // 9 3 | { re: /^\d{1,2}$/i, 4 | handler: function(bits) { 5 | if (bits[0].length == 1) { 6 | return '0' + bits[0] + ':00'; 7 | } else { 8 | return bits[0] + ':00'; 9 | } 10 | } 11 | }, 12 | // 13:00 13 | { re: /^\d{2}[:.]\d{2}$/i, 14 | handler: function(bits) { 15 | return bits[0].replace('.', ':'); 16 | } 17 | }, 18 | // 9:00 19 | { re: /^\d[:.]\d{2}$/i, 20 | handler: function(bits) { 21 | return '0' + bits[0].replace('.', ':'); 22 | } 23 | }, 24 | // 3 am / 3 a.m. / 3am 25 | { re: /^(\d+)\s*([ap])(?:.?m.?)?$/i, 26 | handler: function(bits) { 27 | var hour = parseInt(bits[1]); 28 | if (hour == 12) { 29 | hour = 0; 30 | } 31 | if (bits[2].toLowerCase() == 'p') { 32 | if (hour == 12) { 33 | hour = 0; 34 | } 35 | return (hour + 12) + ':00'; 36 | } else { 37 | if (hour < 10) { 38 | return '0' + hour + ':00'; 39 | } else { 40 | return hour + ':00'; 41 | } 42 | } 43 | } 44 | }, 45 | // 3.30 am / 3:15 a.m. / 3.00am 46 | { re: /^(\d+)[.:](\d{2})\s*([ap]).?m.?$/i, 47 | handler: function(bits) { 48 | var hour = parseInt(bits[1]); 49 | var mins = parseInt(bits[2]); 50 | if (mins < 10) { 51 | mins = '0' + mins; 52 | } 53 | if (hour == 12) { 54 | hour = 0; 55 | } 56 | if (bits[3].toLowerCase() == 'p') { 57 | if (hour == 12) { 58 | hour = 0; 59 | } 60 | return (hour + 12) + ':' + mins; 61 | } else { 62 | if (hour < 10) { 63 | return '0' + hour + ':' + mins; 64 | } else { 65 | return hour + ':' + mins; 66 | } 67 | } 68 | } 69 | }, 70 | // noon 71 | { re: /^no/i, 72 | handler: function(bits) { 73 | return '12:00'; 74 | } 75 | }, 76 | // midnight 77 | { re: /^mid/i, 78 | handler: function(bits) { 79 | return '00:00'; 80 | } 81 | } 82 | ]; 83 | 84 | function parseTimeString(s) { 85 | for (var i = 0; i < timeParsePatterns.length; i++) { 86 | var re = timeParsePatterns[i].re; 87 | var handler = timeParsePatterns[i].handler; 88 | var bits = re.exec(s); 89 | if (bits) { 90 | return handler(bits); 91 | } 92 | } 93 | return s; 94 | } 95 | -------------------------------------------------------------------------------- /static/static_root/admin/js/urlify.js: -------------------------------------------------------------------------------- 1 | var LATIN_MAP = { 2 | 'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'Æ': 'AE', 'Ç': 3 | 'C', 'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E', 'Ì': 'I', 'Í': 'I', 'Î': 'I', 4 | 'Ï': 'I', 'Ð': 'D', 'Ñ': 'N', 'Ò': 'O', 'Ó': 'O', 'Ô': 'O', 'Õ': 'O', 'Ö': 5 | 'O', 'Ő': 'O', 'Ø': 'O', 'Ù': 'U', 'Ú': 'U', 'Û': 'U', 'Ü': 'U', 'Ű': 'U', 6 | 'Ý': 'Y', 'Þ': 'TH', 'ß': 'ss', 'à':'a', 'á':'a', 'â': 'a', 'ã': 'a', 'ä': 7 | 'a', 'å': 'a', 'æ': 'ae', 'ç': 'c', 'è': 'e', 'é': 'e', 'ê': 'e', 'ë': 'e', 8 | 'ì': 'i', 'í': 'i', 'î': 'i', 'ï': 'i', 'ð': 'd', 'ñ': 'n', 'ò': 'o', 'ó': 9 | 'o', 'ô': 'o', 'õ': 'o', 'ö': 'o', 'ő': 'o', 'ø': 'o', 'ù': 'u', 'ú': 'u', 10 | 'û': 'u', 'ü': 'u', 'ű': 'u', 'ý': 'y', 'þ': 'th', 'ÿ': 'y' 11 | } 12 | var LATIN_SYMBOLS_MAP = { 13 | '©':'(c)' 14 | } 15 | var GREEK_MAP = { 16 | 'α':'a', 'β':'b', 'γ':'g', 'δ':'d', 'ε':'e', 'ζ':'z', 'η':'h', 'θ':'8', 17 | 'ι':'i', 'κ':'k', 'λ':'l', 'μ':'m', 'ν':'n', 'ξ':'3', 'ο':'o', 'π':'p', 18 | 'ρ':'r', 'σ':'s', 'τ':'t', 'υ':'y', 'φ':'f', 'χ':'x', 'ψ':'ps', 'ω':'w', 19 | 'ά':'a', 'έ':'e', 'ί':'i', 'ό':'o', 'ύ':'y', 'ή':'h', 'ώ':'w', 'ς':'s', 20 | 'ϊ':'i', 'ΰ':'y', 'ϋ':'y', 'ΐ':'i', 21 | 'Α':'A', 'Β':'B', 'Γ':'G', 'Δ':'D', 'Ε':'E', 'Ζ':'Z', 'Η':'H', 'Θ':'8', 22 | 'Ι':'I', 'Κ':'K', 'Λ':'L', 'Μ':'M', 'Ν':'N', 'Ξ':'3', 'Ο':'O', 'Π':'P', 23 | 'Ρ':'R', 'Σ':'S', 'Τ':'T', 'Υ':'Y', 'Φ':'F', 'Χ':'X', 'Ψ':'PS', 'Ω':'W', 24 | 'Ά':'A', 'Έ':'E', 'Ί':'I', 'Ό':'O', 'Ύ':'Y', 'Ή':'H', 'Ώ':'W', 'Ϊ':'I', 25 | 'Ϋ':'Y' 26 | } 27 | var TURKISH_MAP = { 28 | 'ş':'s', 'Ş':'S', 'ı':'i', 'İ':'I', 'ç':'c', 'Ç':'C', 'ü':'u', 'Ü':'U', 29 | 'ö':'o', 'Ö':'O', 'ğ':'g', 'Ğ':'G' 30 | } 31 | var RUSSIAN_MAP = { 32 | 'а':'a', 'б':'b', 'в':'v', 'г':'g', 'д':'d', 'е':'e', 'ё':'yo', 'ж':'zh', 33 | 'з':'z', 'и':'i', 'й':'j', 'к':'k', 'л':'l', 'м':'m', 'н':'n', 'о':'o', 34 | 'п':'p', 'р':'r', 'с':'s', 'т':'t', 'у':'u', 'ф':'f', 'х':'h', 'ц':'c', 35 | 'ч':'ch', 'ш':'sh', 'щ':'sh', 'ъ':'', 'ы':'y', 'ь':'', 'э':'e', 'ю':'yu', 36 | 'я':'ya', 37 | 'А':'A', 'Б':'B', 'В':'V', 'Г':'G', 'Д':'D', 'Е':'E', 'Ё':'Yo', 'Ж':'Zh', 38 | 'З':'Z', 'И':'I', 'Й':'J', 'К':'K', 'Л':'L', 'М':'M', 'Н':'N', 'О':'O', 39 | 'П':'P', 'Р':'R', 'С':'S', 'Т':'T', 'У':'U', 'Ф':'F', 'Х':'H', 'Ц':'C', 40 | 'Ч':'Ch', 'Ш':'Sh', 'Щ':'Sh', 'Ъ':'', 'Ы':'Y', 'Ь':'', 'Э':'E', 'Ю':'Yu', 41 | 'Я':'Ya' 42 | } 43 | var UKRAINIAN_MAP = { 44 | 'Є':'Ye', 'І':'I', 'Ї':'Yi', 'Ґ':'G', 'є':'ye', 'і':'i', 'ї':'yi', 'ґ':'g' 45 | } 46 | var CZECH_MAP = { 47 | 'č':'c', 'ď':'d', 'ě':'e', 'ň': 'n', 'ř':'r', 'š':'s', 'ť':'t', 'ů':'u', 48 | 'ž':'z', 'Č':'C', 'Ď':'D', 'Ě':'E', 'Ň': 'N', 'Ř':'R', 'Š':'S', 'Ť':'T', 49 | 'Ů':'U', 'Ž':'Z' 50 | } 51 | 52 | var POLISH_MAP = { 53 | 'ą':'a', 'ć':'c', 'ę':'e', 'ł':'l', 'ń':'n', 'ó':'o', 'ś':'s', 'ź':'z', 54 | 'ż':'z', 'Ą':'A', 'Ć':'C', 'Ę':'e', 'Ł':'L', 'Ń':'N', 'Ó':'o', 'Ś':'S', 55 | 'Ź':'Z', 'Ż':'Z' 56 | } 57 | 58 | var LATVIAN_MAP = { 59 | 'ā':'a', 'č':'c', 'ē':'e', 'ģ':'g', 'ī':'i', 'ķ':'k', 'ļ':'l', 'ņ':'n', 60 | 'š':'s', 'ū':'u', 'ž':'z', 'Ā':'A', 'Č':'C', 'Ē':'E', 'Ģ':'G', 'Ī':'i', 61 | 'Ķ':'k', 'Ļ':'L', 'Ņ':'N', 'Š':'S', 'Ū':'u', 'Ž':'Z' 62 | } 63 | 64 | var ALL_DOWNCODE_MAPS=new Array() 65 | ALL_DOWNCODE_MAPS[0]=LATIN_MAP 66 | ALL_DOWNCODE_MAPS[1]=LATIN_SYMBOLS_MAP 67 | ALL_DOWNCODE_MAPS[2]=GREEK_MAP 68 | ALL_DOWNCODE_MAPS[3]=TURKISH_MAP 69 | ALL_DOWNCODE_MAPS[4]=RUSSIAN_MAP 70 | ALL_DOWNCODE_MAPS[5]=UKRAINIAN_MAP 71 | ALL_DOWNCODE_MAPS[6]=CZECH_MAP 72 | ALL_DOWNCODE_MAPS[7]=POLISH_MAP 73 | ALL_DOWNCODE_MAPS[8]=LATVIAN_MAP 74 | 75 | var Downcoder = new Object(); 76 | Downcoder.Initialize = function() 77 | { 78 | if (Downcoder.map) // already made 79 | return ; 80 | Downcoder.map ={} 81 | Downcoder.chars = '' ; 82 | for(var i in ALL_DOWNCODE_MAPS) 83 | { 84 | var lookup = ALL_DOWNCODE_MAPS[i] 85 | for (var c in lookup) 86 | { 87 | Downcoder.map[c] = lookup[c] ; 88 | Downcoder.chars += c ; 89 | } 90 | } 91 | Downcoder.regex = new RegExp('[' + Downcoder.chars + ']|[^' + Downcoder.chars + ']+','g') ; 92 | } 93 | 94 | downcode= function( slug ) 95 | { 96 | Downcoder.Initialize() ; 97 | var downcoded ="" 98 | var pieces = slug.match(Downcoder.regex); 99 | if(pieces) 100 | { 101 | for (var i = 0 ; i < pieces.length ; i++) 102 | { 103 | if (pieces[i].length == 1) 104 | { 105 | var mapped = Downcoder.map[pieces[i]] ; 106 | if (mapped != null) 107 | { 108 | downcoded+=mapped; 109 | continue ; 110 | } 111 | } 112 | downcoded+=pieces[i]; 113 | } 114 | } 115 | else 116 | { 117 | downcoded = slug; 118 | } 119 | return downcoded; 120 | } 121 | 122 | 123 | function URLify(s, num_chars) { 124 | // changes, e.g., "Petty theft" to "petty_theft" 125 | // remove all these words from the string before urlifying 126 | s = downcode(s); 127 | removelist = ["a", "an", "as", "at", "before", "but", "by", "for", "from", 128 | "is", "in", "into", "like", "of", "off", "on", "onto", "per", 129 | "since", "than", "the", "this", "that", "to", "up", "via", 130 | "with"]; 131 | r = new RegExp('\\b(' + removelist.join('|') + ')\\b', 'gi'); 132 | s = s.replace(r, ''); 133 | // if downcode doesn't hit, the char will be stripped here 134 | s = s.replace(/[^-\w\s]/g, ''); // remove unneeded chars 135 | s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces 136 | s = s.replace(/[-\s]+/g, '-'); // convert spaces to hyphens 137 | s = s.toLowerCase(); // convert to lowercase 138 | return s.substring(0, num_chars);// trim to first num_chars chars 139 | } 140 | 141 | -------------------------------------------------------------------------------- /static/static_root/img/placeholder.svg: -------------------------------------------------------------------------------- 1 | 242x200 --------------------------------------------------------------------------------