├── .gitignore ├── README.md ├── ecommerce.code-workspace ├── ecommerce.sublime-project ├── ecommerce.sublime-workspace ├── fasttracktojquery └── index.html ├── notes └── checkout_process.md ├── parse_git_log.py ├── pyvenv.cfg ├── src ├── .gitignore ├── Procfile ├── accounts │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── forms.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_user_full_name.py │ │ ├── 0003_user_is_active.py │ │ ├── 0004_remove_user_active.py │ │ ├── 0005_emailactivation.py │ │ └── __init__.py │ ├── models.py │ ├── passwords │ │ ├── __init__.py │ │ └── urls.py │ ├── signals.py │ ├── templates │ │ └── accounts │ │ │ ├── detail-update-view.html │ │ │ ├── home.html │ │ │ ├── login.html │ │ │ ├── register.html │ │ │ └── snippets │ │ │ └── form.html │ ├── tests.py │ ├── urls.py │ └── views.py ├── addresses │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── forms.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20171107_0055.py │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ └── addresses │ │ │ ├── form.html │ │ │ ├── list.html │ │ │ ├── prev_addresses.html │ │ │ └── update.html │ ├── tests.py │ └── views.py ├── analytics │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_usersession.py │ │ └── __init__.py │ ├── mixins.py │ ├── models.py │ ├── signals.py │ ├── templates │ │ └── analytics │ │ │ └── sales.html │ ├── tests.py │ ├── utils.py │ └── views.py ├── billing │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20170928_2052.py │ │ ├── 0003_billingprofile_customer_id.py │ │ ├── 0004_card.py │ │ ├── 0005_card_default.py │ │ ├── 0006_charge.py │ │ ├── 0007_auto_20171012_1935.py │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ └── billing │ │ │ └── payment-method.html │ ├── tests.py │ └── views.py ├── carts │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_cart_subtotal.py │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ └── carts │ │ │ ├── checkout-done.html │ │ │ ├── checkout.html │ │ │ ├── home.html │ │ │ └── snippets │ │ │ └── remove-product.html │ ├── tests.py │ ├── urls.py │ └── views.py ├── db.sqlite3 ├── db2.sqlite3 ├── ecommerce │ ├── __init__.py │ ├── aws │ │ ├── __init__.py │ │ ├── conf.py │ │ ├── download │ │ │ ├── __init__.py │ │ │ └── utils.py │ │ └── utils.py │ ├── forms.py │ ├── mixins.py │ ├── settings │ │ ├── __init__.py │ │ ├── base.py │ │ ├── local.py │ │ └── production.py │ ├── urls.py │ ├── utils.py │ ├── views.py │ └── wsgi.py ├── manage.py ├── marketing │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── forms.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_marketingpreference_mailchimp_subscribed.py │ │ ├── 0003_auto_20171018_0142.py │ │ └── __init__.py │ ├── mixins.py │ ├── models.py │ ├── tests.py │ ├── utils.py │ └── views.py ├── orders │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20170928_2224.py │ │ ├── 0003_auto_20170929_0013.py │ │ ├── 0004_auto_20171025_2216.py │ │ ├── 0005_auto_20171107_0035.py │ │ ├── 0006_productpurchase_productpurchasemanager.py │ │ ├── 0007_auto_20171108_0028.py │ │ ├── 0008_delete_productpurchasemanager.py │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ └── orders │ │ │ ├── library.html │ │ │ ├── order_detail.html │ │ │ └── order_list.html │ ├── tests.py │ ├── urls.py │ └── views.py ├── products │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── fixtures │ │ └── products.json │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_product_price.py │ │ ├── 0003_product_image.py │ │ ├── 0004_auto_20170901_2159.py │ │ ├── 0005_product_featured.py │ │ ├── 0006_auto_20170901_2254.py │ │ ├── 0007_auto_20170901_2254.py │ │ ├── 0008_auto_20170901_2300.py │ │ ├── 0009_product_timestamp.py │ │ ├── 0010_product_is_digital.py │ │ ├── 0011_productfile.py │ │ ├── 0012_auto_20171108_2325.py │ │ ├── 0013_auto_20171109_0023.py │ │ ├── 0014_auto_20171116_0011.py │ │ ├── 0015_productfile_name.py │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ └── products │ │ │ ├── detail.html │ │ │ ├── featured-detail.html │ │ │ ├── list.html │ │ │ ├── snippets │ │ │ ├── card.html │ │ │ └── update-cart.html │ │ │ └── user-history.html │ ├── tests.py │ ├── understanding_crud.md │ ├── urls.py │ └── views.py ├── requirements.txt ├── runtime.txt ├── search │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ └── search │ │ │ ├── snippets │ │ │ └── search-form.html │ │ │ └── view.html │ ├── tests.py │ ├── urls.py │ └── views.py ├── static_my_proj │ ├── css │ │ ├── main.css │ │ └── stripe-custom-style.css │ ├── img │ │ └── beach.jpg │ └── js │ │ ├── csrf.ajax.js │ │ ├── ecommerce.js │ │ ├── ecommerce.main.js │ │ └── ecommerce.sales.js ├── tags │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── shell_commands.py │ ├── tests.py │ └── views.py └── templates │ ├── 400.html │ ├── 403.html │ ├── 404.html │ ├── 500.html │ ├── base.html │ ├── base │ ├── css.html │ ├── forms.html │ ├── js.html │ ├── js_templates.html │ └── navbar.html │ ├── bootstrap │ └── example.html │ ├── contact │ └── view.html │ ├── home_page.html │ └── registration │ ├── activation-error.html │ ├── emails │ ├── verify.html │ └── verify.txt │ ├── password_change_done.html │ ├── password_change_form.html │ ├── password_reset_complete.html │ ├── password_reset_confirm.html │ ├── password_reset_done.html │ ├── password_reset_email.html │ ├── password_reset_email.txt │ └── password_reset_form.html └── static_cdn ├── media_root └── products │ ├── 470776353 │ └── 470776353.png │ ├── 1130260399 │ └── 1130260399.png │ ├── 1916522416 │ └── 1916522416.png │ ├── 2473283945 │ └── 2473283945.sublime-project │ ├── 2969889474 │ └── 2969889474.jpg │ └── Screen_Shot_2017-09-01_at_2.48.40_PM.png ├── protected_media └── product │ └── my-awesome-album │ ├── basic_audio.m4a │ ├── basic_audio_5W1qjNh.m4a │ ├── basic_audio_5W1qjNh_Ntvw9l5.m4a │ ├── basic_audio_DwLL00o.m4a │ ├── basic_audio_uuyWQIO.m4a │ └── basic_audio_uuyWQIO_soaLtH9.m4a └── static_root ├── admin ├── css │ ├── base.css │ ├── changelists.css │ ├── dashboard.css │ ├── fonts.css │ ├── forms.css │ ├── login.css │ ├── rtl.css │ └── widgets.css ├── fonts │ ├── LICENSE.txt │ ├── README.txt │ ├── Roboto-Bold-webfont.woff │ ├── Roboto-Light-webfont.woff │ └── Roboto-Regular-webfont.woff ├── img │ ├── LICENSE │ ├── README.txt │ ├── calendar-icons.svg │ ├── gis │ │ ├── move_vertex_off.svg │ │ └── move_vertex_on.svg │ ├── icon-addlink.svg │ ├── icon-alert.svg │ ├── icon-calendar.svg │ ├── icon-changelink.svg │ ├── icon-clock.svg │ ├── icon-deletelink.svg │ ├── icon-no.svg │ ├── icon-unknown-alt.svg │ ├── icon-unknown.svg │ ├── icon-yes.svg │ ├── inline-delete.svg │ ├── search.svg │ ├── selector-icons.svg │ ├── sorting-icons.svg │ ├── tooltag-add.svg │ └── tooltag-arrowright.svg └── js │ ├── SelectBox.js │ ├── SelectFilter2.js │ ├── actions.js │ ├── actions.min.js │ ├── admin │ ├── DateTimeShortcuts.js │ └── RelatedObjectLookups.js │ ├── calendar.js │ ├── cancel.js │ ├── change_form.js │ ├── collapse.js │ ├── collapse.min.js │ ├── core.js │ ├── inlines.js │ ├── inlines.min.js │ ├── jquery.init.js │ ├── popup_response.js │ ├── prepopulate.js │ ├── prepopulate.min.js │ ├── prepopulate_init.js │ ├── timeparse.js │ ├── urlify.js │ └── vendor │ ├── jquery │ ├── LICENSE-JQUERY.txt │ ├── jquery.js │ └── jquery.min.js │ └── xregexp │ ├── LICENSE-XREGEXP.txt │ ├── xregexp.js │ └── xregexp.min.js ├── css ├── main.css └── stripe-custom-style.css ├── img └── beach.jpg └── js ├── csrf.ajax.js ├── ecommerce.js └── ecommerce.main.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | 3 | .env 4 | notes/email-activation.py 5 | src/ecommerce/aws/ignore2.py 6 | 7 | # Virtualenv related 8 | bin/ 9 | include/ 10 | pip-selfcheck.json 11 | 12 | # Django related 13 | # src//settings/local.py 14 | # static-cdn/ # any collected static files 15 | 16 | 17 | # Byte-compiled / optimized / DLL files 18 | __pycache__/ 19 | *.py[cod] 20 | *$py.class 21 | 22 | # C extensions 23 | *.so 24 | 25 | # Distribution / packaging 26 | .Python 27 | build/ 28 | develop-eggs/ 29 | dist/ 30 | downloads/ 31 | eggs/ 32 | .eggs/ 33 | lib/ 34 | lib64/ 35 | parts/ 36 | sdist/ 37 | var/ 38 | wheels/ 39 | *.egg-info/ 40 | .installed.cfg 41 | *.egg 42 | 43 | # PyInstaller 44 | # Usually these files are written by a python script from a template 45 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 46 | *.manifest 47 | *.spec 48 | 49 | # Installer logs 50 | pip-log.txt 51 | pip-delete-this-directory.txt 52 | 53 | # Unit test / coverage reports 54 | htmlcov/ 55 | .tox/ 56 | .coverage 57 | .coverage.* 58 | .cache 59 | nosetests.xml 60 | coverage.xml 61 | *.cover 62 | .hypothesis/ 63 | 64 | # Translations 65 | *.mo 66 | *.pot 67 | 68 | # Django stuff: 69 | *.log 70 | local_settings.py 71 | 72 | # Flask stuff: 73 | instance/ 74 | .webassets-cache 75 | 76 | # Scrapy stuff: 77 | .scrapy 78 | 79 | # Sphinx documentation 80 | docs/_build/ 81 | 82 | # PyBuilder 83 | target/ 84 | 85 | # Jupyter Notebook 86 | .ipynb_checkpoints 87 | 88 | # pyenv 89 | .python-version 90 | 91 | # celery beat schedule file 92 | celerybeat-schedule 93 | 94 | # SageMath parsed files 95 | *.sage.py 96 | 97 | # Environments 98 | .env 99 | .venv 100 | env/ 101 | venv/ 102 | ENV/ 103 | 104 | # Spyder project settings 105 | .spyderproject 106 | .spyproject 107 | 108 | # Rope project settings 109 | .ropeproject 110 | 111 | # mkdocs documentation 112 | /site 113 | 114 | # mypy 115 | .mypy_cache/ 116 | 117 | 118 | -------------------------------------------------------------------------------- /ecommerce.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": {} 8 | } -------------------------------------------------------------------------------- /ecommerce.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": "." 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /notes/checkout_process.md: -------------------------------------------------------------------------------- 1 | # Checkout Process 2 | 3 | 1. Cart -> Checkout View 4 | ? 5 | - Login/Register or Enter an Email (as Guest) 6 | - Shipping Address 7 | - Billing Info 8 | - Billing Address 9 | - Credit Card / Payment 10 | 11 | 2. Billing App/Component 12 | - Billing Profile 13 | - User or Email (Guest Email) 14 | - generate payment processor token (Stripe or Braintree) 15 | 16 | 17 | 3. Orders / Invoices Component 18 | - Connecting the Billing Profile 19 | - Shipping / Billing Address 20 | - Cart 21 | - Status -- Shipped? Cancelled? 22 | 23 | 24 | 25 | 4. Backup Fixtures 26 | python manage.py dumpdata products --format json --indent 4 > products/fixtures/products.json -------------------------------------------------------------------------------- /parse_git_log.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This document helps use parse our git commits so we 3 | can make them more easily readable and clickable 4 | in our Readme. 5 | ''' 6 | 7 | import os 8 | import datetime 9 | import git # pip install GitPython 10 | 11 | 12 | repo = git.Repo(os.getcwd()) 13 | 14 | master = repo.head.reference 15 | 16 | with open("parsed_log.md", "w+") as parsed_log: 17 | for commit in master.log(): 18 | if "commit" in commit.message: 19 | commit_mess = commit.message.replace("commit: ", "").replace("commit (initial): ", "") 20 | line = "[{message}](../../tree/{commit}/)".format(message=commit_mess, commit=commit.newhexsha) 21 | parsed_log.write(line) 22 | parsed_log.write("\n\n") 23 | -------------------------------------------------------------------------------- /pyvenv.cfg: -------------------------------------------------------------------------------- 1 | home = /Library/Frameworks/Python.framework/Versions/3.8/bin 2 | include-system-site-packages = false 3 | version = 3.8.2 4 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | # Not Ready for Production 2 | # marketing/ 3 | 4 | .env 5 | 6 | 7 | ecommerce/settings/local.py 8 | ecommerce/aws/ignore2.py 9 | 10 | .DS_STORE 11 | *.sublime-project 12 | fasttracktojquery/ 13 | 14 | 15 | 16 | # Virtualenv related 17 | bin/ 18 | include/ 19 | pip-selfcheck.json 20 | 21 | # Django related 22 | 23 | # src//settings/local.py 24 | # static-cdn/ # any collected static files 25 | 26 | 27 | # Byte-compiled / optimized / DLL files 28 | __pycache__/ 29 | *.py[cod] 30 | *$py.class 31 | 32 | # C extensions 33 | *.so 34 | 35 | # Distribution / packaging 36 | .Python 37 | build/ 38 | develop-eggs/ 39 | dist/ 40 | downloads/ 41 | eggs/ 42 | .eggs/ 43 | lib/ 44 | lib64/ 45 | parts/ 46 | sdist/ 47 | var/ 48 | wheels/ 49 | *.egg-info/ 50 | .installed.cfg 51 | *.egg 52 | 53 | # PyInstaller 54 | # Usually these files are written by a python script from a template 55 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 56 | *.manifest 57 | *.spec 58 | 59 | # Installer logs 60 | pip-log.txt 61 | pip-delete-this-directory.txt 62 | 63 | # Unit test / coverage reports 64 | htmlcov/ 65 | .tox/ 66 | .coverage 67 | .coverage.* 68 | .cache 69 | nosetests.xml 70 | coverage.xml 71 | *.cover 72 | .hypothesis/ 73 | 74 | # Translations 75 | *.mo 76 | *.pot 77 | 78 | # Django stuff: 79 | *.log 80 | local_settings.py 81 | 82 | # Flask stuff: 83 | instance/ 84 | .webassets-cache 85 | 86 | # Scrapy stuff: 87 | .scrapy 88 | 89 | # Sphinx documentation 90 | docs/_build/ 91 | 92 | # PyBuilder 93 | target/ 94 | 95 | # Jupyter Notebook 96 | .ipynb_checkpoints 97 | 98 | # pyenv 99 | .python-version 100 | 101 | # celery beat schedule file 102 | celerybeat-schedule 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | 114 | # Spyder project settings 115 | .spyderproject 116 | .spyproject 117 | 118 | # Rope project settings 119 | .ropeproject 120 | 121 | # mkdocs documentation 122 | /site 123 | 124 | # mypy 125 | .mypy_cache/ -------------------------------------------------------------------------------- /src/Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn ecommerce.wsgi -------------------------------------------------------------------------------- /src/accounts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/accounts/__init__.py -------------------------------------------------------------------------------- /src/accounts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth import get_user_model 3 | from django.contrib.auth.models import Group 4 | from django.contrib.auth.admin import UserAdmin as BaseUserAdmin 5 | 6 | 7 | 8 | from .forms import UserAdminCreationForm, UserAdminChangeForm 9 | from .models import GuestEmail, EmailActivation 10 | 11 | User = get_user_model() 12 | 13 | 14 | 15 | class UserAdmin(BaseUserAdmin): 16 | # The forms to add and change user instances 17 | form = UserAdminChangeForm 18 | add_form = UserAdminCreationForm 19 | 20 | # The fields to be used in displaying the User model. 21 | # These override the definitions on the base UserAdmin 22 | # that reference specific fields on auth.User. 23 | list_display = ('email', 'admin') 24 | list_filter = ('admin', 'staff', 'is_active') 25 | fieldsets = ( 26 | (None, {'fields': ('full_name', 'email', 'password')}), 27 | # ('Full name', {'fields': ()}), 28 | ('Permissions', {'fields': ('admin', 'staff', 'is_active',)}), 29 | ) 30 | # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin 31 | # overrides get_fieldsets to use this attribute when creating a user. 32 | add_fieldsets = ( 33 | (None, { 34 | 'classes': ('wide',), 35 | 'fields': ('email', 'password1', 'password2')} 36 | ), 37 | ) 38 | search_fields = ('email', 'full_name',) 39 | ordering = ('email',) 40 | filter_horizontal = () 41 | 42 | 43 | admin.site.register(User, UserAdmin) 44 | 45 | # Remove Group Model from admin. We're not using it. 46 | admin.site.unregister(Group) 47 | 48 | 49 | 50 | class EmailActivationAdmin(admin.ModelAdmin): 51 | search_fields = ['email'] 52 | class Meta: 53 | model = EmailActivation 54 | 55 | 56 | admin.site.register(EmailActivation, EmailActivationAdmin) 57 | 58 | 59 | class GuestEmailAdmin(admin.ModelAdmin): 60 | search_fields = ['email'] 61 | class Meta: 62 | model = GuestEmail 63 | 64 | 65 | admin.site.register(GuestEmail, GuestEmailAdmin) -------------------------------------------------------------------------------- /src/accounts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AccountsConfig(AppConfig): 5 | name = 'accounts' 6 | -------------------------------------------------------------------------------- /src/accounts/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-10-09 21:27 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='User', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('password', models.CharField(max_length=128, verbose_name='password')), 21 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 22 | ('email', models.EmailField(max_length=255, unique=True)), 23 | ('active', models.BooleanField(default=True)), 24 | ('staff', models.BooleanField(default=False)), 25 | ('admin', models.BooleanField(default=False)), 26 | ('timestamp', models.DateTimeField(auto_now_add=True)), 27 | ], 28 | options={ 29 | 'abstract': False, 30 | }, 31 | ), 32 | migrations.CreateModel( 33 | name='GuestEmail', 34 | fields=[ 35 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 36 | ('email', models.EmailField(max_length=254)), 37 | ('active', models.BooleanField(default=True)), 38 | ('update', models.DateTimeField(auto_now=True)), 39 | ('timestamp', models.DateTimeField(auto_now_add=True)), 40 | ], 41 | ), 42 | ] 43 | -------------------------------------------------------------------------------- /src/accounts/migrations/0002_user_full_name.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-10-09 21:44 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('accounts', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='user', 17 | name='full_name', 18 | field=models.CharField(blank=True, max_length=255, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/accounts/migrations/0003_user_is_active.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.6 on 2017-10-23 22:47 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('accounts', '0002_user_full_name'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='user', 17 | name='is_active', 18 | field=models.BooleanField(default=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/accounts/migrations/0004_remove_user_active.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.6 on 2017-10-24 17:09 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('accounts', '0003_user_is_active'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='user', 17 | name='active', 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /src/accounts/migrations/0005_emailactivation.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.6 on 2017-10-24 18:13 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('accounts', '0004_remove_user_active'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='EmailActivation', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('email', models.EmailField(max_length=254)), 22 | ('key', models.CharField(blank=True, max_length=120, null=True)), 23 | ('activated', models.BooleanField(default=False)), 24 | ('forced_expired', models.BooleanField(default=False)), 25 | ('expires', models.IntegerField(default=7)), 26 | ('timestamp', models.DateTimeField(auto_now_add=True)), 27 | ('update', models.DateTimeField(auto_now=True)), 28 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 29 | ], 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /src/accounts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/accounts/migrations/__init__.py -------------------------------------------------------------------------------- /src/accounts/passwords/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/accounts/passwords/__init__.py -------------------------------------------------------------------------------- /src/accounts/passwords/urls.py: -------------------------------------------------------------------------------- 1 | # accounts.passwords.urls.py 2 | from django.conf.urls import url 3 | from django.contrib.auth import views as auth_views 4 | 5 | urlpatterns = [ 6 | url(r'^password/change/$', 7 | auth_views.PasswordChangeView.as_view(), 8 | name='password_change'), 9 | url(r'^password/change/done/$', 10 | auth_views.PasswordChangeDoneView.as_view(), 11 | name='password_change_done'), 12 | url(r'^password/reset/$', 13 | auth_views.PasswordResetView.as_view(), 14 | name='password_reset'), 15 | url(r'^password/reset/done/$', 16 | auth_views.PasswordResetDoneView.as_view(), 17 | name='password_reset_done'), 18 | url(r'^password/reset/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', 19 | auth_views.PasswordResetConfirmView.as_view(), 20 | name='password_reset_confirm'), 21 | url(r'^password/reset/complete/$', 22 | auth_views.PasswordResetCompleteView.as_view(), 23 | name='password_reset_complete'), 24 | ] -------------------------------------------------------------------------------- /src/accounts/signals.py: -------------------------------------------------------------------------------- 1 | from django.dispatch import Signal 2 | 3 | 4 | user_logged_in = Signal(providing_args=['instance', 'request']) -------------------------------------------------------------------------------- /src/accounts/templates/accounts/detail-update-view.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block content %} 5 |
6 | {% if title %}

{{ title }}

{% endif %} 7 |
{% csrf_token %} 8 | {% if next_url %} 9 | 10 | {% endif %} 11 | {{ form.as_p }} 12 | 13 | Cannot change email
14 | 15 | Change Password 16 |
17 |
18 | {% endblock %} -------------------------------------------------------------------------------- /src/accounts/templates/accounts/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 |

Account

7 |
8 |
9 |
10 |
11 |
12 |

Support

13 |
14 | About 15 | Contact 16 |
17 |
18 |
19 |
20 | 21 | 22 | 23 |
24 |
25 |
26 |

Billing

27 |
28 | Orders 29 | Payment Methods 30 | Addresses 31 | 32 | 33 |
34 |
35 |
36 |
37 | 38 | 39 |
40 |
41 |
42 |

Account Details

43 |
44 | Update Details 45 | Change Password 46 | Library 47 | 48 |
49 | 50 |
51 |
52 |
53 | 54 | 55 |
56 |
57 |
58 |

Preferences

59 |
60 | Email Marketing 61 | 62 |
63 | 64 |
65 |
66 |
67 | 68 | 69 |
70 |
71 |
72 |

History

73 |
74 | Products 75 |
76 |
77 |
78 |
79 | 80 | 81 |
82 | 83 | 84 | {% endblock %} -------------------------------------------------------------------------------- /src/accounts/templates/accounts/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

Login

5 |
{% csrf_token %} 6 | {{ form }} 7 | 8 |
9 | 10 | {% endblock %} -------------------------------------------------------------------------------- /src/accounts/templates/accounts/register.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

Register

5 |
{% csrf_token %} 6 | {{ form }} 7 | 8 |
9 | 10 | {% endblock %} -------------------------------------------------------------------------------- /src/accounts/templates/accounts/snippets/form.html: -------------------------------------------------------------------------------- 1 |
{% csrf_token %} 2 | {% if next_url %} 3 | 4 | {% endif %} 5 | {{ form }} 6 | 7 |
-------------------------------------------------------------------------------- /src/accounts/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/accounts/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from products.views import UserProductHistoryView 4 | from .views import ( 5 | AccountHomeView, 6 | AccountEmailActivateView, 7 | UserDetailUpdateView 8 | ) 9 | 10 | urlpatterns = [ 11 | url(r'^$', AccountHomeView.as_view(), name='home'), 12 | url(r'^details/$', UserDetailUpdateView.as_view(), name='user-update'), 13 | url(r'history/products/$', UserProductHistoryView.as_view(), name='user-product-history'), 14 | url(r'^email/confirm/(?P[0-9A-Za-z]+)/$', 15 | AccountEmailActivateView.as_view(), 16 | name='email-activate'), 17 | url(r'^email/resend-activation/$', 18 | AccountEmailActivateView.as_view(), 19 | name='resend-activation'), 20 | ] 21 | 22 | # account/email/confirm/asdfads/ -> activation view -------------------------------------------------------------------------------- /src/addresses/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/addresses/__init__.py -------------------------------------------------------------------------------- /src/addresses/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Address 4 | 5 | admin.site.register(Address) -------------------------------------------------------------------------------- /src/addresses/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AddressesConfig(AppConfig): 5 | name = 'addresses' 6 | -------------------------------------------------------------------------------- /src/addresses/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from .models import Address 4 | 5 | 6 | class AddressForm(forms.ModelForm): 7 | """ 8 | User-related CRUD form 9 | """ 10 | class Meta: 11 | model = Address 12 | fields = [ 13 | 'nickname', 14 | 'name', 15 | #'billing_profile', 16 | 'address_type', 17 | 'address_line_1', 18 | 'address_line_2', 19 | 'city', 20 | 'country', 21 | 'state', 22 | 'postal_code' 23 | ] 24 | 25 | 26 | 27 | 28 | class AddressCheckoutForm(forms.ModelForm): 29 | """ 30 | User-related checkout address create form 31 | """ 32 | class Meta: 33 | model = Address 34 | fields = [ 35 | 'nickname', 36 | 'name', 37 | #'billing_profile', 38 | #'address_type', 39 | 'address_line_1', 40 | 'address_line_2', 41 | 'city', 42 | 'country', 43 | 'state', 44 | 'postal_code' 45 | ] 46 | 47 | -------------------------------------------------------------------------------- /src/addresses/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-28 23:42 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ('billing', '0002_auto_20170928_2052'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Address', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('address_type', models.CharField(choices=[('billing', 'Billing'), ('shipping', 'Shipping')], max_length=120)), 23 | ('address_line_1', models.CharField(max_length=120)), 24 | ('address_line_2', models.CharField(blank=True, max_length=120, null=True)), 25 | ('city', models.CharField(max_length=120)), 26 | ('country', models.CharField(default='United States of America', max_length=120)), 27 | ('state', models.CharField(max_length=120)), 28 | ('postal_code', models.CharField(max_length=120)), 29 | ('billing_profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='billing.BillingProfile')), 30 | ], 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /src/addresses/migrations/0002_auto_20171107_0055.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.6 on 2017-11-07 00:55 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('addresses', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='address', 17 | name='name', 18 | field=models.CharField(blank=True, help_text='Shipping to? Who is it for?', max_length=120, null=True), 19 | ), 20 | migrations.AddField( 21 | model_name='address', 22 | name='nickname', 23 | field=models.CharField(blank=True, help_text='Internal Reference Nickname', max_length=120, null=True), 24 | ), 25 | migrations.AlterField( 26 | model_name='address', 27 | name='address_type', 28 | field=models.CharField(choices=[('billing', 'Billing address'), ('shipping', 'Shipping address')], max_length=120), 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /src/addresses/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/addresses/migrations/__init__.py -------------------------------------------------------------------------------- /src/addresses/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.core.urlresolvers import reverse 3 | from billing.models import BillingProfile 4 | 5 | ADDRESS_TYPES = ( 6 | ('billing', 'Billing address'), 7 | ('shipping', 'Shipping address'), 8 | ) 9 | 10 | class Address(models.Model): 11 | billing_profile = models.ForeignKey(BillingProfile) 12 | name = models.CharField(max_length=120, null=True, blank=True, help_text='Shipping to? Who is it for?') 13 | nickname = models.CharField(max_length=120, null=True, blank=True, help_text='Internal Reference Nickname') 14 | address_type = models.CharField(max_length=120, choices=ADDRESS_TYPES) 15 | address_line_1 = models.CharField(max_length=120) 16 | address_line_2 = models.CharField(max_length=120, null=True, blank=True) 17 | city = models.CharField(max_length=120) 18 | country = models.CharField(max_length=120, default='United States of America') 19 | state = models.CharField(max_length=120) 20 | postal_code = models.CharField(max_length=120) 21 | 22 | def __str__(self): 23 | if self.nickname: 24 | return str(self.nickname) 25 | return str(self.address_line_1) 26 | 27 | def get_absolute_url(self): 28 | return reverse("address-update", kwargs={"pk": self.pk}) 29 | 30 | def get_short_address(self): 31 | for_name = self.name 32 | if self.nickname: 33 | for_name = "{} | {},".format( self.nickname, for_name) 34 | return "{for_name} {line1}, {city}".format( 35 | for_name = for_name or "", 36 | line1 = self.address_line_1, 37 | city = self.city 38 | ) 39 | 40 | def get_address(self): 41 | return "{for_name}\n{line1}\n{line2}\n{city}\n{state}, {postal}\n{country}".format( 42 | for_name = self.name or "", 43 | line1 = self.address_line_1, 44 | line2 = self.address_line_2 or "", 45 | city = self.city, 46 | state = self.state, 47 | postal= self.postal_code, 48 | country = self.country 49 | ) -------------------------------------------------------------------------------- /src/addresses/templates/addresses/form.html: -------------------------------------------------------------------------------- 1 |
{% csrf_token %} 2 | {% if next_url %} 3 | 4 | {% endif %} 5 | 6 | {% if address_type %} 7 | 8 | {% endif %} 9 | {{ form.as_p }} 10 | 11 |
-------------------------------------------------------------------------------- /src/addresses/templates/addresses/list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 |

Address{% if object_list.count > 1%}es{% endif %} Create New

7 |
8 |
9 |
10 |
11 | {% for obj in object_list %} 12 |
13 |
14 |
15 |

{{ obj }}

16 | {{ obj.get_address_type_display }}
17 |
18 |
Address
19 | {{ obj.get_address|linebreaks }} 20 | 21 | Edit 22 |
23 |
24 |
25 | {% empty %} 26 |

Create New

27 | {% endfor %} 28 |
29 | {% endblock %} -------------------------------------------------------------------------------- /src/addresses/templates/addresses/prev_addresses.html: -------------------------------------------------------------------------------- 1 | {% if address_qs.exists %} 2 |
{% csrf_token %} 3 | {% if next_url %} 4 | 5 | {% endif %} 6 | {% if address_type %} 7 | 8 | {% endif %} 9 | 10 | 11 | {% for address in address_qs %} 12 |
16 | {% endfor %} 17 | 18 |
19 | {% endif %} -------------------------------------------------------------------------------- /src/addresses/templates/addresses/update.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | 5 |
6 |
7 | {% if form.instance.address_line_1 %} 8 |

Update Address

9 | {% else %} 10 |

Create Address

11 | {% endif %} 12 |
13 |
{% csrf_token %} 14 | {{ form.as_p }} 15 | 16 |
17 |
18 | 19 |
20 | 21 | {% endblock %} -------------------------------------------------------------------------------- /src/addresses/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/addresses/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.mixins import LoginRequiredMixin 2 | from django.views.generic import ListView, UpdateView, CreateView 3 | from django.shortcuts import render, redirect 4 | from django.utils.http import is_safe_url 5 | # CRUD create update retrieve delete 6 | 7 | from billing.models import BillingProfile 8 | from .forms import AddressCheckoutForm, AddressForm 9 | from .models import Address 10 | 11 | 12 | 13 | class AddressListView(LoginRequiredMixin, ListView): 14 | template_name = 'addresses/list.html' 15 | 16 | def get_queryset(self): 17 | request = self.request 18 | billing_profile, billing_profile_created = BillingProfile.objects.new_or_get(request) 19 | return Address.objects.filter(billing_profile=billing_profile) 20 | 21 | 22 | 23 | class AddressUpdateView(LoginRequiredMixin, UpdateView): 24 | template_name = 'addresses/update.html' 25 | form_class = AddressForm 26 | success_url = '/addresses' 27 | 28 | def get_queryset(self): 29 | request = self.request 30 | billing_profile, billing_profile_created = BillingProfile.objects.new_or_get(request) 31 | return Address.objects.filter(billing_profile=billing_profile) 32 | 33 | 34 | class AddressCreateView(LoginRequiredMixin, CreateView): 35 | template_name = 'addresses/update.html' 36 | form_class = AddressForm 37 | success_url = '/addresses' 38 | 39 | def form_valid(self, form): 40 | request = self.request 41 | billing_profile, billing_profile_created = BillingProfile.objects.new_or_get(request) 42 | instance = form.save(commit=False) 43 | instance.billing_profile = billing_profile 44 | instance.save() 45 | return super(AddressCreateView, self).form_valid(form) 46 | 47 | # def get_queryset(self): 48 | 49 | # return Address.objects.filter(billing_profile=billing_profile) 50 | 51 | 52 | 53 | 54 | 55 | 56 | def checkout_address_create_view(request): 57 | form = AddressCheckoutForm(request.POST or None) 58 | context = { 59 | "form": form 60 | } 61 | next_ = request.GET.get('next') 62 | next_post = request.POST.get('next') 63 | redirect_path = next_ or next_post or None 64 | if form.is_valid(): 65 | print(request.POST) 66 | instance = form.save(commit=False) 67 | billing_profile, billing_profile_created = BillingProfile.objects.new_or_get(request) 68 | if billing_profile is not None: 69 | address_type = request.POST.get('address_type', 'shipping') 70 | instance.billing_profile = billing_profile 71 | instance.address_type = address_type 72 | instance.save() 73 | request.session[address_type + "_address_id"] = instance.id 74 | print(address_type + "_address_id") 75 | else: 76 | print("Error here") 77 | return redirect("cart:checkout") 78 | 79 | if is_safe_url(redirect_path, request.get_host()): 80 | return redirect(redirect_path) 81 | return redirect("cart:checkout") 82 | 83 | 84 | def checkout_address_reuse_view(request): 85 | if request.user.is_authenticated(): 86 | context = {} 87 | next_ = request.GET.get('next') 88 | next_post = request.POST.get('next') 89 | redirect_path = next_ or next_post or None 90 | if request.method == "POST": 91 | print(request.POST) 92 | shipping_address = request.POST.get('shipping_address', None) 93 | address_type = request.POST.get('address_type', 'shipping') 94 | billing_profile, billing_profile_created = BillingProfile.objects.new_or_get(request) 95 | if shipping_address is not None: 96 | qs = Address.objects.filter(billing_profile=billing_profile, id=shipping_address) 97 | if qs.exists(): 98 | request.session[address_type + "_address_id"] = shipping_address 99 | if is_safe_url(redirect_path, request.get_host()): 100 | return redirect(redirect_path) 101 | return redirect("cart:checkout") 102 | 103 | -------------------------------------------------------------------------------- /src/analytics/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/analytics/__init__.py -------------------------------------------------------------------------------- /src/analytics/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import ObjectViewed, UserSession 4 | 5 | 6 | admin.site.register(ObjectViewed) 7 | 8 | admin.site.register(UserSession) -------------------------------------------------------------------------------- /src/analytics/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AnalyticsConfig(AppConfig): 5 | name = 'analytics' 6 | -------------------------------------------------------------------------------- /src/analytics/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-10-10 21:30 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 | ('contenttypes', '0002_remove_content_type_name'), 17 | ] 18 | 19 | operations = [ 20 | migrations.CreateModel( 21 | name='ObjectViewed', 22 | fields=[ 23 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 24 | ('ip_address', models.CharField(blank=True, max_length=220, null=True)), 25 | ('object_id', models.PositiveIntegerField()), 26 | ('timestamp', models.DateTimeField(auto_now_add=True)), 27 | ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), 28 | ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 29 | ], 30 | options={ 31 | 'verbose_name': 'Object viewed', 32 | 'verbose_name_plural': 'Objects viewed', 33 | 'ordering': ['-timestamp'], 34 | }, 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /src/analytics/migrations/0002_usersession.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-10-10 22:08 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ('analytics', '0001_initial'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='UserSession', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('ip_address', models.CharField(blank=True, max_length=220, null=True)), 23 | ('session_key', models.CharField(blank=True, max_length=100, null=True)), 24 | ('timestamp', models.DateTimeField(auto_now_add=True)), 25 | ('active', models.BooleanField(default=True)), 26 | ('ended', models.BooleanField(default=False)), 27 | ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 28 | ], 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /src/analytics/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/analytics/migrations/__init__.py -------------------------------------------------------------------------------- /src/analytics/mixins.py: -------------------------------------------------------------------------------- 1 | from .signals import object_viewed_signal 2 | 3 | 4 | class ObjectViewedMixin(object): 5 | def get_context_data(self, *args, **kwargs): 6 | context = super(ObjectViewedMixin, self).get_context_data(*args, **kwargs) 7 | request = self.request 8 | instance = context.get('object') 9 | if instance: 10 | object_viewed_signal.send(instance.__class__, instance=instance, request=request) 11 | return context -------------------------------------------------------------------------------- /src/analytics/signals.py: -------------------------------------------------------------------------------- 1 | from django.dispatch import Signal 2 | 3 | 4 | object_viewed_signal = Signal(providing_args=['instance', 'request']) -------------------------------------------------------------------------------- /src/analytics/templates/analytics/sales.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block content %} 5 | 6 | 7 |
8 |
9 |

Sales Data

10 |
11 |
12 | 13 |
14 | 15 |
16 |
17 |

Today's sales

18 |
19 |

Recent Total: ${{ today.recent_data.total__sum }}

20 |
    21 | {% for order in today.recent|slice:":5" %} 22 |
  • Order #{{ order.order_id }}
    23 | ${{ order.total }}
    24 | {{ order.updated|timesince }} ago
  • 25 | {% endfor %} 26 |
27 | 28 |

This week's sales

29 |
30 |

Recent Total: ${{ this_week.recent_data.total__sum }}

31 |
    32 | {% for order in this_week.recent|slice:":5" %} 33 |
  • Order #{{ order.order_id }}
    34 | ${{ order.total }}
    35 | {{ order.updated|timesince }} ago
  • 36 | {% endfor %} 37 |
38 |
39 | 40 |
41 | 42 |
43 |
44 | 45 | 46 | 47 |
48 | 49 |
50 |
51 |

Previous 4 weeks

52 |
53 |
54 |

Orders Total: ${{ last_four_weeks.recent_data.total__sum }}

55 |

Shipped Total: {% if last_four_weeks.shipped_data.total__sum %}${{ last_four_weeks.shipped_data.total__sum }} {% endif %}

56 |

Paid Totals: ${{ last_four_weeks.paid_data.total__sum }}

57 | 58 |
59 |
60 | 61 |
62 |
63 | 64 | 65 | 66 | 67 | 68 | {% endblock %} -------------------------------------------------------------------------------- /src/analytics/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/analytics/utils.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def get_client_ip(request): 4 | x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') 5 | if x_forwarded_for: 6 | ip = x_forwarded_for.split(",")[0] 7 | else: 8 | ip = request.META.get("REMOTE_ADDR", None) 9 | return ip -------------------------------------------------------------------------------- /src/analytics/views.py: -------------------------------------------------------------------------------- 1 | import random 2 | import datetime 3 | from django.contrib.auth.mixins import LoginRequiredMixin 4 | from django.db.models import Count, Sum, Avg 5 | from django.http import HttpResponse, JsonResponse 6 | from django.views.generic import TemplateView, View 7 | from django.shortcuts import render 8 | 9 | from django.utils import timezone 10 | 11 | 12 | from orders.models import Order 13 | 14 | class SalesAjaxView(View): 15 | def get(self, request, *args, **kwargs): 16 | data = {} 17 | if request.user.is_staff: 18 | qs = Order.objects.all().by_weeks_range(weeks_ago=5, number_of_weeks=5) 19 | if request.GET.get('type') == 'week': 20 | days = 7 21 | start_date = timezone.now().today() - datetime.timedelta(days=days-1) 22 | datetime_list = [] 23 | labels = [] 24 | salesItems = [] 25 | for x in range(0, days): 26 | new_time = start_date + datetime.timedelta(days=x) 27 | datetime_list.append( 28 | new_time 29 | ) 30 | labels.append( 31 | new_time.strftime("%a") # mon 32 | ) 33 | new_qs = qs.filter(updated__day=new_time.day, updated__month=new_time.month) 34 | day_total = new_qs.totals_data()['total__sum'] or 0 35 | salesItems.append( 36 | day_total 37 | ) 38 | #print(datetime_list) 39 | 40 | data['labels'] = labels 41 | data['data'] = salesItems 42 | if request.GET.get('type') == '4weeks': 43 | data['labels'] = ["Four Weeks Ago", "Three Weeks Ago", "Two Weeks Ago", "Last Week", "This Week"] 44 | current = 5 45 | data['data'] = [] 46 | for i in range(0, 5): 47 | new_qs = qs.by_weeks_range(weeks_ago=current, number_of_weeks=1) 48 | sales_total = new_qs.totals_data()['total__sum'] or 0 49 | data['data'].append(sales_total) 50 | current -= 1 51 | return JsonResponse(data) 52 | 53 | 54 | 55 | class SalesView(LoginRequiredMixin, TemplateView): 56 | template_name = 'analytics/sales.html' 57 | 58 | def dispatch(self, *args, **kwargs): 59 | user = self.request.user 60 | if not user.is_staff: 61 | return render(self.request, "400.html", {}) 62 | return super(SalesView, self).dispatch(*args, **kwargs) 63 | 64 | 65 | def get_context_data(self, *args, **kwargs): 66 | context = super(SalesView, self).get_context_data(*args, **kwargs) 67 | qs = Order.objects.all().by_weeks_range(weeks_ago=10, number_of_weeks=10) 68 | start_date = timezone.now().date() - datetime.timedelta(hours=24) 69 | end_date = timezone.now().date() + datetime.timedelta(hours=12) 70 | today_data = qs.by_range(start_date=start_date, end_date=end_date).get_sales_breakdown() 71 | context['today'] = today_data 72 | context['this_week'] = qs.by_weeks_range(weeks_ago=1, number_of_weeks=1).get_sales_breakdown() 73 | context['last_four_weeks'] = qs.by_weeks_range(weeks_ago=5, number_of_weeks=4).get_sales_breakdown() 74 | return context -------------------------------------------------------------------------------- /src/billing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/billing/__init__.py -------------------------------------------------------------------------------- /src/billing/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import BillingProfile, Card, Charge 4 | 5 | admin.site.register(BillingProfile) 6 | 7 | admin.site.register(Card) 8 | 9 | admin.site.register(Charge) -------------------------------------------------------------------------------- /src/billing/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BillingConfig(AppConfig): 5 | name = 'billing' 6 | -------------------------------------------------------------------------------- /src/billing/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-28 20:50 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='BillingProfile', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('email', models.EmailField(max_length=254)), 24 | ('active', models.BooleanField(default=True)), 25 | ('update', models.DateTimeField(auto_now=True)), 26 | ('timestamp', models.DateTimeField(auto_now_add=True)), 27 | ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, unique=True)), 28 | ], 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /src/billing/migrations/0002_auto_20170928_2052.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-28 20:52 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('billing', '0001_initial'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name='billingprofile', 19 | name='user', 20 | field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /src/billing/migrations/0003_billingprofile_customer_id.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-10-11 19:13 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('billing', '0002_auto_20170928_2052'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='billingprofile', 17 | name='customer_id', 18 | field=models.CharField(blank=True, max_length=120, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/billing/migrations/0004_card.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-10-11 22:08 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('billing', '0003_billingprofile_customer_id'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Card', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('stripe_id', models.CharField(max_length=120)), 21 | ('brand', models.CharField(blank=True, max_length=120, null=True)), 22 | ('country', models.CharField(blank=True, max_length=20, null=True)), 23 | ('exp_month', models.IntegerField(blank=True, null=True)), 24 | ('exp_year', models.IntegerField(blank=True, null=True)), 25 | ('last4', models.CharField(blank=True, max_length=4, null=True)), 26 | ('billing_profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='billing.BillingProfile')), 27 | ], 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /src/billing/migrations/0005_card_default.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-10-11 22:10 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('billing', '0004_card'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='card', 17 | name='default', 18 | field=models.BooleanField(default=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/billing/migrations/0006_charge.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-10-12 00:06 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('billing', '0005_card_default'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Charge', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('stripe_id', models.CharField(max_length=120)), 21 | ('paid', models.BooleanField(default=False)), 22 | ('refunded', models.BooleanField(default=False)), 23 | ('outcome', models.TextField(blank=True, null=True)), 24 | ('outcome_type', models.CharField(blank=True, max_length=120, null=True)), 25 | ('seller_message', models.CharField(blank=True, max_length=120, null=True)), 26 | ('risk_level', models.CharField(blank=True, max_length=120, null=True)), 27 | ('billing_profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='billing.BillingProfile')), 28 | ], 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /src/billing/migrations/0007_auto_20171012_1935.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-10-12 19:35 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.utils.timezone 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('billing', '0006_charge'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='card', 18 | name='active', 19 | field=models.BooleanField(default=True), 20 | ), 21 | migrations.AddField( 22 | model_name='card', 23 | name='timestamp', 24 | field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), 25 | preserve_default=False, 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /src/billing/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/billing/migrations/__init__.py -------------------------------------------------------------------------------- /src/billing/templates/billing/payment-method.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | 4 | {% block content %} 5 | 6 |
7 |

Add Payment Method

8 |
9 |
10 | 11 | 12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /src/billing/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/billing/views.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.http import JsonResponse, HttpResponse 3 | from django.shortcuts import render, redirect 4 | from django.utils.http import is_safe_url 5 | 6 | 7 | 8 | 9 | import stripe 10 | STRIPE_SECRET_KEY = getattr(settings, "STRIPE_SECRET_KEY", "sk_test_cu1lQmcg1OLffhLvYrSCp5XE") 11 | STRIPE_PUB_KEY = getattr(settings, "STRIPE_PUB_KEY", 'pk_test_PrV61avxnHaWIYZEeiYTTVMZ') 12 | stripe.api_key = STRIPE_SECRET_KEY 13 | 14 | 15 | 16 | from .models import BillingProfile, Card 17 | 18 | def payment_method_view(request): 19 | #next_url = 20 | # if request.user.is_authenticated(): 21 | # billing_profile = request.user.billingprofile 22 | # my_customer_id = billing_profile.customer_id 23 | 24 | billing_profile, billing_profile_created = BillingProfile.objects.new_or_get(request) 25 | if not billing_profile: 26 | return redirect("/cart") 27 | next_url = None 28 | next_ = request.GET.get('next') 29 | if is_safe_url(next_, request.get_host()): 30 | next_url = next_ 31 | return render(request, 'billing/payment-method.html', {"publish_key": STRIPE_PUB_KEY, "next_url": next_url}) 32 | 33 | 34 | 35 | 36 | def payment_method_createview(request): 37 | if request.method == "POST" and request.is_ajax(): 38 | billing_profile, billing_profile_created = BillingProfile.objects.new_or_get(request) 39 | if not billing_profile: 40 | return HttpResponse({"message": "Cannot find this user"}, status_code=401) 41 | token = request.POST.get("token") 42 | if token is not None: 43 | new_card_obj = Card.objects.add_new(billing_profile, token) 44 | return JsonResponse({"message": "Success! Your card was added."}) 45 | return HttpResponse("error", status_code=401) 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/carts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/carts/__init__.py -------------------------------------------------------------------------------- /src/carts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | 4 | from .models import Cart 5 | 6 | admin.site.register(Cart) -------------------------------------------------------------------------------- /src/carts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CartsConfig(AppConfig): 5 | name = 'carts' 6 | -------------------------------------------------------------------------------- /src/carts/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-26 00:27 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | ('products', '0009_product_timestamp'), 16 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 17 | ] 18 | 19 | operations = [ 20 | migrations.CreateModel( 21 | name='Cart', 22 | fields=[ 23 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 24 | ('total', models.DecimalField(decimal_places=2, default=0.0, max_digits=100)), 25 | ('updated', models.DateTimeField(auto_now=True)), 26 | ('timestamp', models.DateTimeField(auto_now_add=True)), 27 | ('products', models.ManyToManyField(blank=True, to='products.Product')), 28 | ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 29 | ], 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /src/carts/migrations/0002_cart_subtotal.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-26 01:23 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('carts', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='cart', 17 | name='subtotal', 18 | field=models.DecimalField(decimal_places=2, default=0.0, max_digits=100), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/carts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/carts/migrations/__init__.py -------------------------------------------------------------------------------- /src/carts/models.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from django.conf import settings 3 | from django.db import models 4 | from django.db.models.signals import pre_save, post_save, m2m_changed 5 | 6 | from products.models import Product 7 | 8 | 9 | User = settings.AUTH_USER_MODEL 10 | 11 | class CartManager(models.Manager): 12 | def new_or_get(self, request): 13 | cart_id = request.session.get("cart_id", None) 14 | qs = self.get_queryset().filter(id=cart_id) 15 | if qs.count() == 1: 16 | new_obj = False 17 | cart_obj = qs.first() 18 | if request.user.is_authenticated() and cart_obj.user is None: 19 | cart_obj.user = request.user 20 | cart_obj.save() 21 | else: 22 | cart_obj = Cart.objects.new(user=request.user) 23 | new_obj = True 24 | request.session['cart_id'] = cart_obj.id 25 | return cart_obj, new_obj 26 | 27 | def new(self, user=None): 28 | user_obj = None 29 | if user is not None: 30 | if user.is_authenticated(): 31 | user_obj = user 32 | return self.model.objects.create(user=user_obj) 33 | 34 | class Cart(models.Model): 35 | user = models.ForeignKey(User, null=True, blank=True) 36 | products = models.ManyToManyField(Product, blank=True) 37 | subtotal = models.DecimalField(default=0.00, max_digits=100, decimal_places=2) 38 | total = models.DecimalField(default=0.00, max_digits=100, decimal_places=2) 39 | updated = models.DateTimeField(auto_now=True) 40 | timestamp = models.DateTimeField(auto_now_add=True) 41 | 42 | objects = CartManager() 43 | 44 | def __str__(self): 45 | return str(self.id) 46 | 47 | @property 48 | def is_digital(self): 49 | qs = self.products.all() #every product 50 | new_qs = qs.filter(is_digital=False) # every product that is not digial 51 | if new_qs.exists(): 52 | return False 53 | return True 54 | 55 | 56 | 57 | 58 | def m2m_changed_cart_receiver(sender, instance, action, *args, **kwargs): 59 | if action == 'post_add' or action == 'post_remove' or action == 'post_clear': 60 | products = instance.products.all() 61 | total = 0 62 | for x in products: 63 | total += x.price 64 | if instance.subtotal != total: 65 | instance.subtotal = total 66 | instance.save() 67 | 68 | m2m_changed.connect(m2m_changed_cart_receiver, sender=Cart.products.through) 69 | 70 | 71 | 72 | 73 | def pre_save_cart_receiver(sender, instance, *args, **kwargs): 74 | if instance.subtotal > 0: 75 | instance.total = Decimal(instance.subtotal) * Decimal(1.08) # 8% tax 76 | else: 77 | instance.total = 0.00 78 | 79 | pre_save.connect(pre_save_cart_receiver, sender=Cart) 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/carts/templates/carts/checkout-done.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block content %} 5 |
6 |

Thank you for your order!!!

7 |
8 | 9 | {% endblock %} -------------------------------------------------------------------------------- /src/carts/templates/carts/checkout.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block content %} 5 | {% if not billing_profile %} 6 |
7 |
8 |

Login

9 | {% include 'accounts/snippets/form.html' with form=login_form next_url=request.build_absolute_uri %} 10 |
11 |
12 | Continue as Guest 13 | 14 | {% url "guest_register" as guest_register_url %} 15 | {% include 'accounts/snippets/form.html' with form=guest_form next_url=request.build_absolute_uri action_url=guest_register_url %} 16 |
17 | 18 |
19 | 20 | {% else %} 21 | 22 | {% if not object.shipping_address and shipping_address_required %} 23 | 24 |
25 |
26 |

Shipping Address

27 |
28 |
29 |
30 | 31 | 32 | 33 | 34 | {% url "checkout_address_create" as checkout_address_create %} 35 | {% include 'addresses/form.html' with form=address_form next_url=request.build_absolute_uri action_url=checkout_address_create address_type='shipping' %}' 36 | 37 | 38 | 39 |
40 |
41 | {% url 'checkout_address_reuse' as checkout_address_reuse %} 42 | {% include 'addresses/prev_addresses.html' with address_qs=address_qs next_url=request.build_absolute_uri address_type='shipping' action_url=checkout_address_reuse %} 43 |
44 |
45 | 46 | 47 | {% elif not object.billing_address %} 48 |
49 |
50 |

Billing Address

51 |
52 |
53 |
54 | 55 | {% url "checkout_address_create" as checkout_address_create %} 56 | {% include 'addresses/form.html' with form=address_form next_url=request.build_absolute_uri action_url=checkout_address_create address_type='billing' %} 57 |
58 |
59 | {% url 'checkout_address_reuse' as checkout_address_reuse %} 60 | {% include 'addresses/prev_addresses.html' with address_qs=address_qs next_url=request.build_absolute_uri address_type='billing' action_url=checkout_address_reuse %} 61 |
62 | 63 | 64 |
65 | {% else %} 66 | {% if not has_card %} 67 | 68 |
69 | 70 | {% else %} 71 |

Finalize Checkout

72 |

Cart Items: {% for product in object.cart.products.all %}{{ product }}{% if not forloop.last %}, {% endif %}{% endfor %}

73 |

Shipping Address: {{ object.shipping_address_final }}

74 |

Billing Address: {{ object.billing_address_final }}

75 |

Payment Method: {{ billing_profile.default_card }} (Change)

76 |

Cart Total: {{ object.cart.total }}

77 |

Shipping Total: {{ object.shipping_total }}

78 |

Order Total: {{ object.total }}

79 | 80 |
{% csrf_token %} 81 | 82 |
83 | {% endif %} 84 | {% endif %} 85 | {% endif %} 86 | 87 | 88 | 89 | {% endblock %} -------------------------------------------------------------------------------- /src/carts/templates/carts/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block content %} 5 |

Cart

6 | 7 | {% if cart.products.exists %} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% for product in cart.products.all %} 18 | 19 | 20 | 23 | 24 | 25 | {% endfor %} 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
#Product NameProduct Price
{{ forloop.counter }}{{ product.title }} 21 | {% include 'carts/snippets/remove-product.html' with product_id=product.id %} 22 | {{ product.price }}
Subtotal ${{ cart.subtotal }}
Total ${{ cart.total }}
Checkout
41 | 42 | 46 | 47 | {% else %} 48 |

Cart is empty

49 | {% endif %} 50 | 51 | 52 | {% endblock %} -------------------------------------------------------------------------------- /src/carts/templates/carts/snippets/remove-product.html: -------------------------------------------------------------------------------- 1 |
{% csrf_token %} 2 | 3 | 4 | 5 |
-------------------------------------------------------------------------------- /src/carts/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/carts/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from .views import ( 4 | cart_home, 5 | cart_update, 6 | checkout_home, 7 | checkout_done_view 8 | ) 9 | 10 | urlpatterns = [ 11 | url(r'^$', cart_home, name='home'), 12 | url(r'^checkout/success/$', checkout_done_view, name='success'), 13 | url(r'^checkout/$', checkout_home, name='checkout'), 14 | url(r'^update/$', cart_update, name='update'), 15 | ] -------------------------------------------------------------------------------- /src/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/db.sqlite3 -------------------------------------------------------------------------------- /src/db2.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/db2.sqlite3 -------------------------------------------------------------------------------- /src/ecommerce/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/ecommerce/__init__.py -------------------------------------------------------------------------------- /src/ecommerce/aws/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/ecommerce/aws/__init__.py -------------------------------------------------------------------------------- /src/ecommerce/aws/conf.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import os 3 | 4 | try: 5 | from .ignore2 import AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY 6 | except: 7 | AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID", "AKIAJARK375PALZJC55Q") 8 | AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY", "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") 9 | 10 | AWS_GROUP_NAME = "CFE_eCommerce_Group" 11 | AWS_USERNAME = "cfe-ecommerce-user" 12 | 13 | AWS_FILE_EXPIRE = 200 14 | AWS_PRELOAD_METADATA = True 15 | AWS_QUERYSTRING_AUTH = False 16 | 17 | DEFAULT_FILE_STORAGE = 'ecommerce.aws.utils.MediaRootS3BotoStorage' 18 | STATICFILES_STORAGE = 'ecommerce.aws.utils.StaticRootS3BotoStorage' 19 | AWS_STORAGE_BUCKET_NAME = 'cfe-ecommerce' 20 | S3DIRECT_REGION = 'us-west-2' 21 | S3_URL = '//%s.s3.amazonaws.com/' % AWS_STORAGE_BUCKET_NAME 22 | MEDIA_URL = '//%s.s3.amazonaws.com/media/' % AWS_STORAGE_BUCKET_NAME 23 | MEDIA_ROOT = MEDIA_URL 24 | STATIC_URL = S3_URL + 'static/' 25 | ADMIN_MEDIA_PREFIX = STATIC_URL + 'admin/' 26 | 27 | two_months = datetime.timedelta(days=61) 28 | date_two_months_later = datetime.date.today() + two_months 29 | expires = date_two_months_later.strftime("%A, %d %B %Y 20:00:00 GMT") 30 | 31 | AWS_HEADERS = { 32 | 'Expires': expires, 33 | 'Cache-Control': 'max-age=%d' % (int(two_months.total_seconds()), ), 34 | } 35 | 36 | PROTECTED_DIR_NAME = 'protected' 37 | PROTECTED_MEDIA_URL = '//%s.s3.amazonaws.com/%s/' %( AWS_STORAGE_BUCKET_NAME, PROTECTED_DIR_NAME) 38 | 39 | AWS_DOWNLOAD_EXPIRE = 5000 #(0ptional, in milliseconds) 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/ecommerce/aws/download/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/ecommerce/aws/download/__init__.py -------------------------------------------------------------------------------- /src/ecommerce/aws/download/utils.py: -------------------------------------------------------------------------------- 1 | import boto 2 | import re 3 | import os 4 | 5 | from django.conf import settings 6 | from boto.s3.connection import OrdinaryCallingFormat 7 | 8 | 9 | class AWSDownload(object): 10 | access_key = None 11 | secret_key = None 12 | bucket = None 13 | region = None 14 | expires = getattr(settings, 'AWS_DOWNLOAD_EXPIRE', 5000) 15 | 16 | def __init__(self, access_key, secret_key, bucket, region, *args, **kwargs): 17 | self.bucket = bucket 18 | self.access_key = access_key 19 | self.secret_key = secret_key 20 | self.region = region 21 | super(AWSDownload, self).__init__(*args, **kwargs) 22 | 23 | def s3connect(self): 24 | conn = boto.s3.connect_to_region( 25 | self.region, 26 | aws_access_key_id=self.access_key, 27 | aws_secret_access_key=self.secret_key, 28 | is_secure=True, 29 | calling_format=OrdinaryCallingFormat() 30 | ) 31 | return conn 32 | 33 | def get_bucket(self): 34 | conn = self.s3connect() 35 | bucket_name = self.bucket 36 | bucket = conn.get_bucket(bucket_name) 37 | return bucket 38 | 39 | def get_key(self, path): 40 | bucket = self.get_bucket() 41 | key = bucket.get_key(path) 42 | return key 43 | 44 | def get_filename(self, path, new_filename=None): 45 | current_filename = os.path.basename(path) 46 | if new_filename is not None: 47 | filename, file_extension = os.path.splitext(current_filename) 48 | escaped_new_filename_base = re.sub( 49 | '[^A-Za-z0-9\#]+', 50 | '-', 51 | new_filename) 52 | escaped_filename = escaped_new_filename_base + file_extension 53 | return escaped_filename 54 | return current_filename 55 | 56 | def generate_url(self, path, download=True, new_filename=None): 57 | file_url = None 58 | aws_obj_key = self.get_key(path) 59 | if aws_obj_key: 60 | headers = None 61 | if download: 62 | filename = self.get_filename(path, new_filename=new_filename) 63 | headers = { 64 | 'response-content-type': 'application/force-download', 65 | 'response-content-disposition':'attachment;filename="%s"'%filename 66 | } 67 | file_url = aws_obj_key.generate_url( 68 | response_headers=headers, 69 | expires_in=self.expires, 70 | method='GET') 71 | return file_url -------------------------------------------------------------------------------- /src/ecommerce/aws/utils.py: -------------------------------------------------------------------------------- 1 | from storages.backends.s3boto3 import S3Boto3Storage 2 | 3 | StaticRootS3BotoStorage = lambda: S3Boto3Storage(location='static') 4 | MediaRootS3BotoStorage = lambda: S3Boto3Storage(location='media') 5 | ProtectedS3Storage = lambda: S3Boto3Storage(location='protected') -------------------------------------------------------------------------------- /src/ecommerce/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.auth import get_user_model 3 | 4 | User = get_user_model() 5 | 6 | class ContactForm(forms.Form): 7 | fullname = forms.CharField( 8 | widget=forms.TextInput( 9 | attrs={ 10 | "class": "form-control", 11 | "placeholder": "Your full name" 12 | } 13 | ) 14 | ) 15 | email = forms.EmailField( 16 | widget=forms.EmailInput( 17 | attrs={ 18 | "class": "form-control", 19 | "placeholder": "Your email" 20 | } 21 | ) 22 | ) 23 | content = forms.CharField( 24 | widget=forms.Textarea( 25 | attrs={ 26 | 'class': 'form-control', 27 | "placeholder": "Your message" 28 | } 29 | ) 30 | ) 31 | 32 | # def clean_email(self): 33 | # email = self.cleaned_data.get("email") 34 | # if not "gmail.com" in email: 35 | # raise forms.ValidationError("Email has to be gmail.com") 36 | # return email 37 | 38 | # def clean_content(self): 39 | # raise forms.ValidationError("Content is wrong.") 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/ecommerce/mixins.py: -------------------------------------------------------------------------------- 1 | from django.utils.http import is_safe_url 2 | 3 | 4 | class RequestFormAttachMixin(object): 5 | def get_form_kwargs(self): 6 | kwargs = super(RequestFormAttachMixin, self).get_form_kwargs() 7 | kwargs['request'] = self.request 8 | return kwargs 9 | 10 | 11 | class NextUrlMixin(object): 12 | default_next = "/" 13 | def get_next_url(self): 14 | request = self.request 15 | next_ = request.GET.get('next') 16 | next_post = request.POST.get('next') 17 | redirect_path = next_ or next_post or None 18 | if is_safe_url(redirect_path, request.get_host()): 19 | return redirect_path 20 | return self.default_next -------------------------------------------------------------------------------- /src/ecommerce/settings/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import * 2 | 3 | from .production import * 4 | 5 | try: 6 | from .local import * 7 | except: 8 | pass 9 | 10 | try: 11 | from .local_justin import * 12 | except: 13 | pass -------------------------------------------------------------------------------- /src/ecommerce/utils.py: -------------------------------------------------------------------------------- 1 | 2 | import datetime 3 | import os 4 | import random 5 | import string 6 | 7 | from django.utils import timezone 8 | from django.utils.text import slugify 9 | 10 | 11 | 12 | def get_last_month_data(today): 13 | ''' 14 | Simple method to get the datetime objects for the 15 | start and end of last month. 16 | ''' 17 | this_month_start = datetime.datetime(today.year, today.month, 1) 18 | last_month_end = this_month_start - datetime.timedelta(days=1) 19 | last_month_start = datetime.datetime(last_month_end.year, last_month_end.month, 1) 20 | return (last_month_start, last_month_end) 21 | 22 | 23 | def get_month_data_range(months_ago=1, include_this_month=False): 24 | ''' 25 | A method that generates a list of dictionaires 26 | that describe any given amout of monthly data. 27 | ''' 28 | today = datetime.datetime.now().today() 29 | dates_ = [] 30 | if include_this_month: 31 | # get next month's data with: 32 | next_month = today.replace(day=28) + datetime.timedelta(days=4) 33 | # use next month's data to get this month's data breakdown 34 | start, end = get_last_month_data(next_month) 35 | dates_.insert(0, { 36 | "start": start.timestamp(), 37 | "end": end.timestamp(), 38 | "start_json": start.isoformat(), 39 | "end": end.timestamp(), 40 | "end_json": end.isoformat(), 41 | "timesince": 0, 42 | "year": start.year, 43 | "month": str(start.strftime("%B")), 44 | }) 45 | for x in range(0, months_ago): 46 | start, end = get_last_month_data(today) 47 | today = start 48 | dates_.insert(0, { 49 | "start": start.timestamp(), 50 | "start_json": start.isoformat(), 51 | "end": end.timestamp(), 52 | "end_json": end.isoformat(), 53 | "timesince": int((datetime.datetime.now() - end).total_seconds()), 54 | "year": start.year, 55 | "month": str(start.strftime("%B")) 56 | }) 57 | #dates_.reverse() 58 | return dates_ 59 | 60 | 61 | def get_filename(path): #/abc/filename.mp4 62 | return os.path.basename(path) 63 | 64 | 65 | def random_string_generator(size=10, chars=string.ascii_lowercase + string.digits): 66 | return ''.join(random.choice(chars) for _ in range(size)) 67 | 68 | 69 | def unique_key_generator(instance): 70 | """ 71 | This is for a Django project with an key field 72 | """ 73 | size = random.randint(30, 45) 74 | key = random_string_generator(size=size) 75 | 76 | Klass = instance.__class__ 77 | qs_exists = Klass.objects.filter(key=key).exists() 78 | if qs_exists: 79 | return unique_slug_generator(instance) 80 | return key 81 | 82 | 83 | def unique_order_id_generator(instance): 84 | """ 85 | This is for a Django project with an order_id field 86 | """ 87 | order_new_id = random_string_generator() 88 | 89 | Klass = instance.__class__ 90 | qs_exists = Klass.objects.filter(order_id=order_new_id).exists() 91 | if qs_exists: 92 | return unique_slug_generator(instance) 93 | return order_new_id 94 | 95 | 96 | 97 | 98 | 99 | def unique_slug_generator(instance, new_slug=None): 100 | """ 101 | This is for a Django project and it assumes your instance 102 | has a model with a slug field and a title character (char) field. 103 | """ 104 | if new_slug is not None: 105 | slug = new_slug 106 | else: 107 | slug = slugify(instance.title) 108 | 109 | Klass = instance.__class__ 110 | qs_exists = Klass.objects.filter(slug=slug).exists() 111 | if qs_exists: 112 | new_slug = "{slug}-{randstr}".format( 113 | slug=slug, 114 | randstr=random_string_generator(size=4) 115 | ) 116 | return unique_slug_generator(instance, new_slug=new_slug) 117 | return slug -------------------------------------------------------------------------------- /src/ecommerce/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import authenticate, login, get_user_model 2 | from django.http import HttpResponse, JsonResponse 3 | from django.shortcuts import render,redirect 4 | 5 | from .forms import ContactForm 6 | 7 | def home_page(request): 8 | # print(request.session.get("first_name", "Unknown")) 9 | # request.session['first_name'] 10 | context = { 11 | "title":"Hello World!", 12 | "content":" Welcome to the homepage.", 13 | 14 | } 15 | if request.user.is_authenticated(): 16 | context["premium_content"] = "YEAHHHHHH" 17 | return render(request, "home_page.html", context) 18 | 19 | def about_page(request): 20 | context = { 21 | "title":"About Page", 22 | "content":" Welcome to the about page." 23 | } 24 | return render(request, "home_page.html", context) 25 | 26 | def contact_page(request): 27 | contact_form = ContactForm(request.POST or None) 28 | context = { 29 | "title":"Contact", 30 | "content":" Welcome to the contact page.", 31 | "form": contact_form, 32 | } 33 | if contact_form.is_valid(): 34 | print(contact_form.cleaned_data) 35 | if request.is_ajax(): 36 | return JsonResponse({"message": "Thank you for your submission"}) 37 | 38 | if contact_form.errors: 39 | errors = contact_form.errors.as_json() 40 | if request.is_ajax(): 41 | return HttpResponse(errors, status=400, content_type='application/json') 42 | 43 | # if request.method == "POST": 44 | # #print(request.POST) 45 | # print(request.POST.get('fullname')) 46 | # print(request.POST.get('email')) 47 | # print(request.POST.get('content')) 48 | return render(request, "contact/view.html", context) 49 | 50 | 51 | 52 | 53 | 54 | def home_page_old(request): 55 | html_ = """ 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 |
68 |

Hello, world!

69 |
70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | """ 78 | return HttpResponse(html_) 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/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.11/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ecommerce.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /src/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 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /src/marketing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/marketing/__init__.py -------------------------------------------------------------------------------- /src/marketing/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import MarketingPreference 4 | 5 | class MarketingPreferenceAdmin(admin.ModelAdmin): 6 | list_display = ['__str__', 'subscribed', 'updated'] 7 | readonly_fields = ['mailchimp_msg', 'mailchimp_subscribed', 'timestamp', 'updated'] 8 | class Meta: 9 | model = MarketingPreference 10 | fields = [ 11 | 'user', 12 | 'subscribed', 13 | 'mailchimp_msg', 14 | 'mailchimp_subscribed', 15 | 'timestamp', 16 | 'updated' 17 | ] 18 | 19 | admin.site.register(MarketingPreference, MarketingPreferenceAdmin) -------------------------------------------------------------------------------- /src/marketing/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MarketingConfig(AppConfig): 5 | name = 'marketing' 6 | -------------------------------------------------------------------------------- /src/marketing/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from .models import MarketingPreference 4 | 5 | 6 | class MarketingPreferenceForm(forms.ModelForm): 7 | subscribed = forms.BooleanField(label='Receive Marketing Email?', required=False) 8 | class Meta: 9 | model = MarketingPreference 10 | fields = [ 11 | 'subscribed' 12 | ] -------------------------------------------------------------------------------- /src/marketing/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-10-18 00:36 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='MarketingPreference', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('subscribed', models.BooleanField(default=True)), 24 | ('mailchimp_msg', models.TextField(blank=True, null=True)), 25 | ('timestamp', models.DateTimeField(auto_now_add=True)), 26 | ('update', models.DateTimeField(auto_now=True)), 27 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 28 | ], 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /src/marketing/migrations/0002_marketingpreference_mailchimp_subscribed.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-10-18 01:34 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('marketing', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='marketingpreference', 17 | name='mailchimp_subscribed', 18 | field=models.NullBooleanField(), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/marketing/migrations/0003_auto_20171018_0142.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-10-18 01:42 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('marketing', '0002_marketingpreference_mailchimp_subscribed'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='marketingpreference', 17 | old_name='update', 18 | new_name='updated', 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/marketing/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/marketing/migrations/__init__.py -------------------------------------------------------------------------------- /src/marketing/mixins.py: -------------------------------------------------------------------------------- 1 | from django.utils.decorators import method_decorator 2 | from django.views.decorators.csrf import csrf_exempt 3 | 4 | 5 | class CsrfExemptMixin(object): 6 | @method_decorator(csrf_exempt) 7 | def dispatch(self, request, *args, **kwargs): 8 | return super(CsrfExemptMixin, self).dispatch(request, *args, **kwargs) 9 | 10 | -------------------------------------------------------------------------------- /src/marketing/models.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.db import models 3 | from django.db.models.signals import post_save, pre_save 4 | 5 | from .utils import Mailchimp 6 | 7 | class MarketingPreference(models.Model): 8 | user = models.OneToOneField(settings.AUTH_USER_MODEL) 9 | subscribed = models.BooleanField(default=True) 10 | mailchimp_subscribed = models.NullBooleanField(blank=True) 11 | mailchimp_msg = models.TextField(null=True, blank=True) 12 | timestamp = models.DateTimeField(auto_now_add=True) 13 | updated = models.DateTimeField(auto_now=True) 14 | 15 | def __str__(self): 16 | return self.user.email 17 | 18 | 19 | 20 | 21 | def marketing_pref_create_receiver(sender, instance, created, *args, **kwargs): 22 | if created: 23 | status_code, response_data = Mailchimp().subscribe(instance.user.email) 24 | print(status_code, response_data) 25 | 26 | 27 | post_save.connect(marketing_pref_create_receiver, sender=MarketingPreference) 28 | 29 | def marketing_pref_update_receiver(sender, instance, *args, **kwargs): 30 | if instance.subscribed != instance.mailchimp_subscribed: 31 | if instance.subscribed: 32 | # subscribing user 33 | status_code, response_data = Mailchimp().subscribe(instance.user.email) 34 | else: 35 | # unsubscribing user 36 | status_code, response_data = Mailchimp().unsubscribe(instance.user.email) 37 | 38 | if response_data['status'] == 'subscribed': 39 | instance.subscribed = True 40 | instance.mailchimp_subscribed = True 41 | instance.mailchimp_msg = response_data 42 | else: 43 | instance.subscribed = False 44 | instance.mailchimp_subscribed = False 45 | instance.mailchimp_msg = response_data 46 | 47 | pre_save.connect(marketing_pref_update_receiver, sender=MarketingPreference) 48 | 49 | 50 | 51 | def make_marketing_pref_receiver(sender, instance, created, *args, **kwargs): 52 | ''' 53 | User model 54 | ''' 55 | if created: 56 | MarketingPreference.objects.get_or_create(user=instance) 57 | 58 | post_save.connect(make_marketing_pref_receiver, sender=settings.AUTH_USER_MODEL) 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/marketing/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/marketing/utils.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import json 3 | import re 4 | import requests 5 | from django.conf import settings 6 | 7 | 8 | MAILCHIMP_API_KEY = getattr(settings, "MAILCHIMP_API_KEY", None) 9 | MAILCHIMP_DATA_CENTER = getattr(settings, "MAILCHIMP_DATA_CENTER", None) 10 | MAILCHIMP_EMAIL_LIST_ID = getattr(settings, "MAILCHIMP_EMAIL_LIST_ID", None) 11 | 12 | 13 | 14 | def check_email(email): 15 | if not re.match(r".+@.+\..+", email): 16 | raise ValueError('String passed is not a valid email address') 17 | return email 18 | 19 | def get_subscriber_hash(member_email): 20 | check_email(member_email) 21 | member_email = member_email.lower().encode() 22 | m = hashlib.md5(member_email) 23 | return m.hexdigest() 24 | 25 | 26 | 27 | class Mailchimp(object): 28 | def __init__(self): 29 | super(Mailchimp, self).__init__() 30 | self.key = MAILCHIMP_API_KEY 31 | self.api_url = "https://{dc}.api.mailchimp.com/3.0".format( 32 | dc=MAILCHIMP_DATA_CENTER 33 | ) 34 | self.list_id = MAILCHIMP_EMAIL_LIST_ID 35 | self.list_endpoint = '{api_url}/lists/{list_id}'.format( 36 | api_url = self.api_url, 37 | list_id=self.list_id 38 | ) 39 | 40 | 41 | def get_members_endpoint(self): 42 | return self.list_endpoint + "/members" 43 | 44 | def change_subcription_status(self, email, status='unsubscribed'): 45 | hashed_email = get_subscriber_hash(email) 46 | endpoint = self.get_members_endpoint() + "/" + hashed_email 47 | data = { 48 | "status": self.check_valid_status(status) 49 | } 50 | r = requests.put(endpoint, auth=("", self.key), data=json.dumps(data)) 51 | return r.status_code, r.json() 52 | 53 | 54 | def check_subcription_status(self, email): 55 | hashed_email = get_subscriber_hash(email) 56 | endpoint = self.get_members_endpoint() + "/" + hashed_email 57 | r = requests.get(endpoint, auth=("", self.key)) 58 | return r.status_code, r.json() 59 | 60 | def check_valid_status(self, status): 61 | choices = ['subscribed', 'unsubscribed', 'cleaned', 'pending'] 62 | if status not in choices: 63 | raise ValueError("Not a valid choice for email status") 64 | return status 65 | 66 | def add_email(self, email): 67 | # status = "subscribed" 68 | # self.check_valid_status(status) 69 | # data = { 70 | # "email_address": email, 71 | # "status": status 72 | # } 73 | # endpoint = self.get_members_endpoint() 74 | # r = requests.post(endpoint, auth=("", self.key), data=json.dumps(data)) 75 | return self.change_subcription_status(email, status='subscribed') 76 | 77 | def unsubscribe(self, email): 78 | return self.change_subcription_status(email, status='unsubscribed') 79 | 80 | def subscribe(self, email): 81 | return self.change_subcription_status(email, status='subscribed') 82 | 83 | def pending(self, email): 84 | return self.change_subcription_status(email, status='pending') 85 | 86 | -------------------------------------------------------------------------------- /src/orders/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/orders/__init__.py -------------------------------------------------------------------------------- /src/orders/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Order, ProductPurchase 4 | 5 | admin.site.register(Order) 6 | 7 | admin.site.register(ProductPurchase) -------------------------------------------------------------------------------- /src/orders/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class OrdersConfig(AppConfig): 5 | name = 'orders' 6 | -------------------------------------------------------------------------------- /src/orders/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-27 23:00 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ('carts', '0002_cart_subtotal'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Order', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('order_id', models.CharField(blank=True, max_length=120)), 23 | ('status', models.CharField(choices=[('created', 'Created'), ('paid', 'Paid'), ('shipped', 'Shipped'), ('refunded', 'Refunded')], default='created', max_length=120)), 24 | ('shipping_total', models.DecimalField(decimal_places=2, default=5.99, max_digits=100)), 25 | ('total', models.DecimalField(decimal_places=2, default=0.0, max_digits=100)), 26 | ('cart', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='carts.Cart')), 27 | ], 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /src/orders/migrations/0002_auto_20170928_2224.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-28 22:24 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('billing', '0002_auto_20170928_2052'), 13 | ('orders', '0001_initial'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AddField( 18 | model_name='order', 19 | name='active', 20 | field=models.BooleanField(default=True), 21 | ), 22 | migrations.AddField( 23 | model_name='order', 24 | name='billing_profile', 25 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='billing.BillingProfile'), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /src/orders/migrations/0003_auto_20170929_0013.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-29 00:13 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('addresses', '0001_initial'), 13 | ('orders', '0002_auto_20170928_2224'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AddField( 18 | model_name='order', 19 | name='billing_address', 20 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='billing_address', to='addresses.Address'), 21 | ), 22 | migrations.AddField( 23 | model_name='order', 24 | name='shipping_address', 25 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='shipping_address', to='addresses.Address'), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /src/orders/migrations/0004_auto_20171025_2216.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.6 on 2017-10-25 22:16 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.utils.timezone 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('orders', '0003_auto_20170929_0013'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterModelOptions( 17 | name='order', 18 | options={'ordering': ['-timestamp', '-updated']}, 19 | ), 20 | migrations.AddField( 21 | model_name='order', 22 | name='timestamp', 23 | field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), 24 | preserve_default=False, 25 | ), 26 | migrations.AddField( 27 | model_name='order', 28 | name='updated', 29 | field=models.DateTimeField(auto_now=True), 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /src/orders/migrations/0005_auto_20171107_0035.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.6 on 2017-11-07 00:35 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('orders', '0004_auto_20171025_2216'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='order', 17 | name='billing_address_final', 18 | field=models.TextField(blank=True, null=True), 19 | ), 20 | migrations.AddField( 21 | model_name='order', 22 | name='shipping_address_final', 23 | field=models.TextField(blank=True, null=True), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /src/orders/migrations/0006_productpurchase_productpurchasemanager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.6 on 2017-11-08 00:17 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('products', '0010_product_is_digital'), 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ('billing', '0007_auto_20171012_1935'), 16 | ('orders', '0005_auto_20171107_0035'), 17 | ] 18 | 19 | operations = [ 20 | migrations.CreateModel( 21 | name='ProductPurchase', 22 | fields=[ 23 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 24 | ('refunded', models.BooleanField(default=False)), 25 | ('updated', models.DateTimeField(auto_now=True)), 26 | ('timestamp', models.DateTimeField(auto_now_add=True)), 27 | ('billing_profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='billing.BillingProfile')), 28 | ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='products.Product')), 29 | ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 30 | ], 31 | ), 32 | migrations.CreateModel( 33 | name='ProductPurchaseManager', 34 | fields=[ 35 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 36 | ], 37 | ), 38 | ] 39 | -------------------------------------------------------------------------------- /src/orders/migrations/0007_auto_20171108_0028.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.6 on 2017-11-08 00:28 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('orders', '0006_productpurchase_productpurchasemanager'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='productpurchase', 17 | name='user', 18 | ), 19 | migrations.AddField( 20 | model_name='productpurchase', 21 | name='order_id', 22 | field=models.CharField(default='abc123', max_length=120), 23 | preserve_default=False, 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /src/orders/migrations/0008_delete_productpurchasemanager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.6 on 2017-11-08 00:39 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('orders', '0007_auto_20171108_0028'), 12 | ] 13 | 14 | operations = [ 15 | migrations.DeleteModel( 16 | name='ProductPurchaseManager', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /src/orders/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/orders/migrations/__init__.py -------------------------------------------------------------------------------- /src/orders/templates/orders/library.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block content %} 5 | 6 | 7 |
8 |
9 |

Library

10 |
11 |
12 |
13 |
14 | 15 | 16 | 17 | 18 | {% for object in object_list %} 19 | 20 | 21 | 25 | 26 | {% empty %} 27 | 28 | {% endfor %} 29 | 30 | 31 |
ProductDownload
{{ object.title }}{% for download in object.get_downloads %} 22 | {{ download.display_name }}
23 | {% endfor %} 24 |

No orders yet.

32 |
33 |
34 | 35 | 36 | 37 | {% endblock %} -------------------------------------------------------------------------------- /src/orders/templates/orders/order_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block content %} 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |

Order {{ object.order_id }}

13 |
14 |
15 |
16 | 17 |
18 |
19 |

Items: {% for product in object.cart.products.all %}{{ product }}{% if not forloop.last %}, {% endif %}{% endfor %}

20 |

Shipping Address: {{ object.shipping_address_final }}

21 |

Billing Address: {{ object.billing_address_final }}

22 |

Subtotal: {{ object.cart.total }}

23 |

Shipping Total: {{ object.shipping_total }}

24 |

Order Total: {{ object.total }}

25 |

Order Status: {{ object.get_status }}

26 |
27 | 28 | 29 | {% endblock %} -------------------------------------------------------------------------------- /src/orders/templates/orders/order_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block content %} 5 | 6 | 7 |
8 |
9 |

Orders

10 |
11 |
12 |
13 |
14 | 15 | 16 | 17 | 18 | {% for object in object_list %} 19 | 20 | 21 | 22 | 23 | 24 | {% empty %} 25 | 26 | {% endfor %} 27 | 28 | 29 |
Order IdStatusTotal
{{ object.order_id }}{{ object.get_status }}{{ object.total }}

No orders yet.

30 |
31 |
32 | 33 | 34 | 35 | {% endblock %} -------------------------------------------------------------------------------- /src/orders/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/orders/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from .views import ( 4 | OrderListView, 5 | OrderDetailView, 6 | VerifyOwnership 7 | ) 8 | 9 | urlpatterns = [ 10 | url(r'^$', OrderListView.as_view(), name='list'), 11 | url(r'^endpoint/verify/ownership/$', VerifyOwnership.as_view(), name='verify-ownership'), 12 | url(r'^(?P[0-9A-Za-z]+)/$', OrderDetailView.as_view(), name='detail'), 13 | ] -------------------------------------------------------------------------------- /src/orders/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.mixins import LoginRequiredMixin 2 | from django.http import Http404, JsonResponse 3 | from django.views.generic import View, ListView, DetailView 4 | from django.shortcuts import render 5 | 6 | from billing.models import BillingProfile 7 | from .models import Order, ProductPurchase 8 | 9 | 10 | class OrderListView(LoginRequiredMixin, ListView): 11 | 12 | def get_queryset(self): 13 | return Order.objects.by_request(self.request).not_created() 14 | 15 | 16 | class OrderDetailView(LoginRequiredMixin, DetailView): 17 | 18 | def get_object(self): 19 | #return Order.objects.get(id=self.kwargs.get('id')) 20 | #return Order.objects.get(slug=self.kwargs.get('slug')) 21 | qs = Order.objects.by_request( 22 | self.request 23 | ).filter( 24 | order_id = self.kwargs.get('order_id') 25 | ) 26 | if qs.count() == 1: 27 | return qs.first() 28 | raise Http404 29 | 30 | 31 | 32 | class LibraryView(LoginRequiredMixin, ListView): 33 | template_name = 'orders/library.html' 34 | def get_queryset(self): 35 | return ProductPurchase.objects.products_by_request(self.request) #.by_request(self.request).digital() 36 | 37 | 38 | class VerifyOwnership(View): 39 | def get(self, request, *args, **kwargs): 40 | if request.is_ajax(): 41 | data = request.GET 42 | product_id = request.GET.get('product_id', None) 43 | if product_id is not None: 44 | product_id = int(product_id) 45 | ownership_ids = ProductPurchase.objects.products_by_id(request) 46 | if product_id in ownership_ids: 47 | return JsonResponse({'owner': True}) 48 | return JsonResponse({'owner': False}) 49 | raise Http404 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/products/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/products/__init__.py -------------------------------------------------------------------------------- /src/products/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Product, ProductFile 4 | 5 | 6 | class ProductFileInline(admin.TabularInline): 7 | model = ProductFile 8 | extra = 1 9 | 10 | 11 | class ProductAdmin(admin.ModelAdmin): 12 | list_display = ['__str__', 'slug', 'is_digital'] 13 | inlines = [ProductFileInline] 14 | class Meta: 15 | model = Product 16 | 17 | admin.site.register(Product, ProductAdmin) -------------------------------------------------------------------------------- /src/products/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ProductsConfig(AppConfig): 5 | name = 'products' 6 | -------------------------------------------------------------------------------- /src/products/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-01 19:03 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Product', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('title', models.CharField(max_length=120)), 21 | ('description', models.TextField()), 22 | ], 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /src/products/migrations/0002_product_price.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-01 19:07 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('products', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='product', 17 | name='price', 18 | field=models.DecimalField(decimal_places=2, default=39.99, max_digits=20), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/products/migrations/0003_product_image.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-01 21:48 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('products', '0002_product_price'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='product', 17 | name='image', 18 | field=models.FileField(blank=True, null=True, upload_to='products/'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/products/migrations/0004_auto_20170901_2159.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-01 21:59 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import products.models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('products', '0003_product_image'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='product', 18 | name='image', 19 | field=models.ImageField(blank=True, null=True, upload_to=products.models.upload_image_path), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /src/products/migrations/0005_product_featured.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-01 22:39 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('products', '0004_auto_20170901_2159'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='product', 17 | name='featured', 18 | field=models.BooleanField(default=False), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/products/migrations/0006_auto_20170901_2254.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-01 22:54 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('products', '0005_product_featured'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='product', 17 | name='active', 18 | field=models.BooleanField(default=True), 19 | ), 20 | migrations.AddField( 21 | model_name='product', 22 | name='slug', 23 | field=models.SlugField(default='abc'), 24 | preserve_default=False, 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /src/products/migrations/0007_auto_20170901_2254.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-01 22:54 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('products', '0006_auto_20170901_2254'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='product', 17 | name='slug', 18 | field=models.SlugField(blank=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/products/migrations/0008_auto_20170901_2300.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-01 23:00 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('products', '0007_auto_20170901_2254'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='product', 17 | name='slug', 18 | field=models.SlugField(blank=True, unique=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/products/migrations/0009_product_timestamp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-18 19:28 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.utils.timezone 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('products', '0008_auto_20170901_2300'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='product', 18 | name='timestamp', 19 | field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), 20 | preserve_default=False, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /src/products/migrations/0010_product_is_digital.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.6 on 2017-11-07 22:47 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('products', '0009_product_timestamp'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='product', 17 | name='is_digital', 18 | field=models.BooleanField(default=False), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/products/migrations/0011_productfile.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.6 on 2017-11-08 23:07 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('products', '0010_product_is_digital'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='ProductFile', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('file', models.FileField(upload_to='products/')), 21 | ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='products.Product')), 22 | ], 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /src/products/migrations/0012_auto_20171108_2325.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.6 on 2017-11-08 23:25 3 | from __future__ import unicode_literals 4 | 5 | import django.core.files.storage 6 | from django.db import migrations, models 7 | import products.models 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('products', '0011_productfile'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name='productfile', 19 | name='file', 20 | field=models.FileField(storage=django.core.files.storage.FileSystemStorage(location='/Users/cfe/Dev/ecommerce/static_cdn/protected_media'), upload_to=products.models.upload_product_file_loc), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /src/products/migrations/0013_auto_20171109_0023.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.6 on 2017-11-09 00:23 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('products', '0012_auto_20171108_2325'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='productfile', 17 | name='free', 18 | field=models.BooleanField(default=False), 19 | ), 20 | migrations.AddField( 21 | model_name='productfile', 22 | name='user_required', 23 | field=models.BooleanField(default=False), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /src/products/migrations/0014_auto_20171116_0011.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.6 on 2017-11-16 00:11 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import products.models 7 | import storages.backends.s3boto3 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('products', '0013_auto_20171109_0023'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name='productfile', 19 | name='file', 20 | field=models.FileField(storage=storages.backends.s3boto3.S3Boto3Storage(location='protected'), upload_to=products.models.upload_product_file_loc), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /src/products/migrations/0015_productfile_name.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.6 on 2017-11-16 00:12 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('products', '0014_auto_20171116_0011'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='productfile', 17 | name='name', 18 | field=models.CharField(blank=True, max_length=120, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/products/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/products/migrations/__init__.py -------------------------------------------------------------------------------- /src/products/templates/products/detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block content %} 5 | 6 | 7 |
8 |
9 |

{{ object.title }}

10 | {{ object.timestamp|timesince }} ago 11 | {{ object.description|linebreaks }}
12 | {% if object.image %} 13 | 14 | {% endif %} 15 |
16 |
17 | {% include 'products/snippets/update-cart.html' with product=object cart=cart %} 18 | 19 |
20 |
21 | 22 | 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /src/products/templates/products/featured-detail.html: -------------------------------------------------------------------------------- 1 | {{ object.title }}
2 | {{ object.description }}
3 | {% if object.image %} 4 | 5 | {% endif %} -------------------------------------------------------------------------------- /src/products/templates/products/list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 | 6 | {% for obj in object_list %} 7 |
8 | {% include 'products/snippets/card.html' with instance=obj %} 9 | 10 |
11 | {% endfor %} 12 |
13 | {% endblock %} -------------------------------------------------------------------------------- /src/products/templates/products/snippets/card.html: -------------------------------------------------------------------------------- 1 |
2 | {% if instance.image %} 3 | {{ instance.title}} logo 4 | {% endif %} 5 |
6 |

{{ instance.title }}

7 |

{{ instance.description|linebreaks|truncatewords:14 }}

8 | 9 |
10 | View 11 | {% include 'products/snippets/update-cart.html' with product=instance cart=cart %} 12 |
13 | 14 |
15 |
-------------------------------------------------------------------------------- /src/products/templates/products/snippets/update-cart.html: -------------------------------------------------------------------------------- 1 |
{% csrf_token %} 2 | 3 | 4 | {% if product in cart.products.all %} 5 |
In cart
6 | {% else %} 7 | 8 | {% endif %} 9 |
10 |
-------------------------------------------------------------------------------- /src/products/templates/products/user-history.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 |

Product View History

7 |
8 |
9 | {% for obj in object_list %} 10 |
11 | {{ forloop.counter }} 12 | {% include 'products/snippets/card.html' with instance=obj.content_object %} 13 |
14 | {% endfor %} 15 |
16 | {% endblock %} -------------------------------------------------------------------------------- /src/products/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/products/understanding_crud.md: -------------------------------------------------------------------------------- 1 | ### Understanding CRUD 2 | 3 | 4 | Create -- POST 5 | Retrieve / List / Search -- GET 6 | Update -- PUT / Patch / POST 7 | Delete -- Delete 8 | -------------------------------------------------------------------------------- /src/products/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from .views import ( 4 | ProductListView, 5 | ProductDetailSlugView, 6 | ProductDownloadView 7 | ) 8 | 9 | urlpatterns = [ 10 | url(r'^$', ProductListView.as_view(), name='list'), 11 | url(r'^(?P[\w-]+)/$', ProductDetailSlugView.as_view(), name='detail'), 12 | url(r'^(?P[\w-]+)/(?P\d+)/$', ProductDownloadView.as_view(), name='download'), 13 | ] 14 | 15 | -------------------------------------------------------------------------------- /src/requirements.txt: -------------------------------------------------------------------------------- 1 | boto3==1.4.7 2 | boto==2.48.0 3 | botocore==1.7.32 4 | certifi==2017.7.27.1 5 | chardet==3.0.4 6 | dj-database-url 7 | django-storages 8 | django==1.11.17 9 | docutils==0.14 10 | gitdb2==2.0.3 11 | gitpython==2.1.7 12 | gunicorn==19.7.1 13 | idna==2.6 14 | jmespath==0.9.3 15 | olefile==0.44 16 | pillow 17 | pip==9.0.1 18 | psycopg2-binary 19 | python-dateutil==2.6.1 20 | pytz 21 | requests>=2.20.0 22 | s3transfer==0.1.11 23 | setuptools==36.6.0 24 | six==1.11.0 25 | smmap2==2.0.3 26 | stripe 27 | urllib3>=1.23 28 | wheel==0.30.0 29 | -------------------------------------------------------------------------------- /src/runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.6.2 -------------------------------------------------------------------------------- /src/search/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/search/__init__.py -------------------------------------------------------------------------------- /src/search/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /src/search/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class SearchConfig(AppConfig): 5 | name = 'search' 6 | -------------------------------------------------------------------------------- /src/search/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/search/migrations/__init__.py -------------------------------------------------------------------------------- /src/search/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /src/search/templates/search/snippets/search-form.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 |
8 |
-------------------------------------------------------------------------------- /src/search/templates/search/view.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 | {% if query %} 6 |
7 | Results for {{ query }} 8 |
9 |
10 | {% else %} 11 | 12 |
13 | {% include 'search/snippets/search-form.html' %} 14 | 15 |
16 |
17 |
18 |
19 | {% endif %} 20 |
21 | 22 | 23 |
24 | 25 | 26 | 27 | {% for obj in object_list %} 28 |
29 | 30 | {% include 'products/snippets/card.html' with instance=obj %} 31 | 32 | {% if forloop.counter|divisibleby:3 %} 33 |

34 | {% elif forloop.counter|divisibleby:2 %} 35 |

36 | {% else %} 37 |
38 | {% endif %} 39 | 40 | 41 | {% endfor %} 42 | 43 | {% endblock %} -------------------------------------------------------------------------------- /src/search/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/search/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from .views import ( 4 | SearchProductView 5 | ) 6 | 7 | urlpatterns = [ 8 | url(r'^$', SearchProductView.as_view(), name='query'), 9 | ] 10 | 11 | -------------------------------------------------------------------------------- /src/search/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.views.generic import ListView 3 | from products.models import Product 4 | 5 | class SearchProductView(ListView): 6 | template_name = "search/view.html" 7 | 8 | def get_context_data(self, *args, **kwargs): 9 | context = super(SearchProductView, self).get_context_data(*args, **kwargs) 10 | query = self.request.GET.get('q') 11 | context['query'] = query 12 | # SearchQuery.objects.create(query=query) 13 | return context 14 | 15 | def get_queryset(self, *args, **kwargs): 16 | request = self.request 17 | method_dict = request.GET 18 | query = method_dict.get('q', None) # method_dict['q'] 19 | if query is not None: 20 | return Product.objects.search(query) 21 | return Product.objects.featured() 22 | ''' 23 | __icontains = field contains this 24 | __iexact = fields is exactly this 25 | ''' 26 | -------------------------------------------------------------------------------- /src/static_my_proj/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #ccc; 3 | } -------------------------------------------------------------------------------- /src/static_my_proj/css/stripe-custom-style.css: -------------------------------------------------------------------------------- 1 | /** 2 | * The CSS shown here will not be introduced in the Quickstart guide, but shows 3 | * how you can use CSS to style your Element's container. 4 | */ 5 | .StripeElement { 6 | background-color: white; 7 | padding: 8px 12px; 8 | border-radius: 4px; 9 | border: 1px solid transparent; 10 | box-shadow: 0 1px 3px 0 #e6ebf1; 11 | -webkit-transition: box-shadow 150ms ease; 12 | transition: box-shadow 150ms ease; 13 | } 14 | 15 | .StripeElement--focus { 16 | box-shadow: 0 1px 3px 0 #cfd7df; 17 | } 18 | 19 | .StripeElement--invalid { 20 | border-color: #fa755a; 21 | } 22 | 23 | .StripeElement--webkit-autofill { 24 | background-color: #fefde5 !important; 25 | } 26 | -------------------------------------------------------------------------------- /src/static_my_proj/img/beach.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/static_my_proj/img/beach.jpg -------------------------------------------------------------------------------- /src/static_my_proj/js/csrf.ajax.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | // using jQuery 3 | function getCookie(name) { 4 | var cookieValue = null; 5 | if (document.cookie && document.cookie !== '') { 6 | var cookies = document.cookie.split(';'); 7 | for (var i = 0; i < cookies.length; i++) { 8 | var cookie = jQuery.trim(cookies[i]); 9 | // Does this cookie string begin with the name we want? 10 | if (cookie.substring(0, name.length + 1) === (name + '=')) { 11 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 12 | break; 13 | } 14 | } 15 | } 16 | return cookieValue; 17 | } 18 | var csrftoken = getCookie('csrftoken'); 19 | 20 | function csrfSafeMethod(method) { 21 | // these HTTP methods do not require CSRF protection 22 | return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 23 | } 24 | $.ajaxSetup({ 25 | beforeSend: function(xhr, settings) { 26 | if (!csrfSafeMethod(settings.type) && !this.crossDomain) { 27 | xhr.setRequestHeader("X-CSRFToken", csrftoken); 28 | } 29 | } 30 | }); 31 | 32 | }) -------------------------------------------------------------------------------- /src/static_my_proj/js/ecommerce.sales.js: -------------------------------------------------------------------------------- 1 | 2 | $(document).ready(function(){ 3 | function renderChart(id, data, labels){ 4 | // var ctx = document.getElementById("myChart").getContext('2d'); 5 | var ctx = $('#' + id) 6 | var myChart = new Chart(ctx, { 7 | type: 'line', 8 | data: { 9 | labels: labels, 10 | datasets: [{ 11 | label: 'Sales', 12 | data: data, 13 | backgroundColor: 'rgba(0, 158, 29, 0.45)', 14 | borderColor:'rgba(0, 158, 29, 1)', 15 | }] 16 | }, 17 | options: { 18 | scales: { 19 | yAxes: [{ 20 | ticks: { 21 | beginAtZero:true 22 | } 23 | }] 24 | }, 25 | backgroundColor: 'rgba(75, 192, 192, 1)' 26 | } 27 | }); 28 | } 29 | 30 | function getSalesData(id, type){ 31 | var url = '/analytics/sales/data/' 32 | var method = 'GET' 33 | var data = {"type": type} 34 | $.ajax({ 35 | url: url, 36 | method: method, 37 | data: data, 38 | success: function(responseData){ 39 | renderChart(id, responseData.data, responseData.labels) 40 | }, error: function(error){ 41 | $.alert("An error occurred") 42 | } 43 | }) 44 | } 45 | var chartsToRender = $('.cfe-render-chart') 46 | $.each(chartsToRender, function(index, html){ 47 | var $this = $(this) 48 | if ( $this.attr('id') && $this.attr('data-type')){ 49 | getSalesData($this.attr('id'), $this.attr('data-type')) 50 | } 51 | 52 | }) 53 | 54 | }) -------------------------------------------------------------------------------- /src/tags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/tags/__init__.py -------------------------------------------------------------------------------- /src/tags/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Tag 4 | 5 | admin.site.register(Tag) -------------------------------------------------------------------------------- /src/tags/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class TagsConfig(AppConfig): 5 | name = 'tags' 6 | -------------------------------------------------------------------------------- /src/tags/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-21 00:00 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ('products', '0009_product_timestamp'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Tag', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('title', models.CharField(max_length=120)), 22 | ('slug', models.SlugField()), 23 | ('timestamp', models.DateTimeField(auto_now_add=True)), 24 | ('active', models.BooleanField(default=True)), 25 | ('products', models.ManyToManyField(blank=True, to='products.Product')), 26 | ], 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /src/tags/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/src/tags/migrations/__init__.py -------------------------------------------------------------------------------- /src/tags/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.db.models.signals import pre_save, post_save 3 | from django.urls import reverse 4 | 5 | from ecommerce.utils import unique_slug_generator 6 | from products.models import Product 7 | 8 | 9 | class Tag(models.Model): 10 | title = models.CharField(max_length=120) 11 | slug = models.SlugField() 12 | timestamp = models.DateTimeField(auto_now_add=True) 13 | active = models.BooleanField(default=True) 14 | products = models.ManyToManyField(Product, blank=True) 15 | 16 | def __str__(self): 17 | return self.title 18 | 19 | 20 | def tag_pre_save_receiver(sender, instance, *args, **kwargs): 21 | if not instance.slug: 22 | instance.slug = unique_slug_generator(instance) 23 | 24 | pre_save.connect(tag_pre_save_receiver, sender=Tag) -------------------------------------------------------------------------------- /src/tags/shell_commands.py: -------------------------------------------------------------------------------- 1 | ''' 2 | # Shell session 1 3 | # python manage.py shell 4 | ''' 5 | 6 | from tags.models import Tag 7 | 8 | qs = Tag.objects.all() 9 | print(qs) 10 | black = Tag.objects.last() 11 | black.title 12 | black.slug 13 | 14 | black.products 15 | """ 16 | Reutrns: 17 | .ManyRelatedManager object at 0x1112f3fd0> 18 | """ 19 | 20 | black.products.all() 21 | """ 22 | This is an actual queryset of PRODUCTS 23 | Much like Products.objects.all(), but in this case it's ALL of the products that are 24 | related to the "Black" tag 25 | """ 26 | black.products.all().first() 27 | """ 28 | returns the first instance, if any 29 | """ 30 | 31 | exit() 32 | 33 | ''' 34 | # Shell session 2 35 | # python manage.ppy shell 36 | ''' 37 | from products.models import Product 38 | 39 | 40 | 41 | qs = Product.objects.all() 42 | print(qs) 43 | tshirt = qs.first() 44 | tshirt.title 45 | tshirt.description 46 | 47 | tshirt.tag 48 | ''' 49 | Raises an error because the Product model doens't have a field "tag" 50 | ''' 51 | 52 | tshirt.tags 53 | ''' 54 | Raises an error because the Product model doens't have a field "tags" 55 | ''' 56 | 57 | tshirt.tag_set 58 | ''' 59 | This works because the Tag model has the "products" field with the ManyToMany to Product 60 | .ManyRelatedManager object at 0x10c0e75f8> 61 | ''' 62 | 63 | tshirt.tag_set.all() 64 | ''' 65 | Returns an actual Queryset of the Tag model related to this product 66 | , , , , ]> 67 | ''' 68 | tshirt.tag_set.filter(title__icontains='black') 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/tags/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/tags/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /src/templates/400.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block content %} 5 |
6 |
7 | 8 |

Bad Request

9 |

400 Error

10 | 11 |
12 |
13 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/403.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block content %} 5 |
6 |
7 | 8 | 9 |

Permission Denied

10 |

403 Error

11 | 12 |
13 |
14 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block content %} 5 |
6 |
7 | 8 |

Page Not Found

9 |

404 Error

10 | 11 |
12 |
13 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block content %} 5 |
6 |
7 | 8 |

Oops. Sorry. A Server Error Occured

9 |

500 Error | We have been notified and are working on a fix.

10 | 11 |
12 |
13 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | Base Template 9 | {% include 'base/css.html' %} 10 | {% block base_head %}{% endblock %} 11 | 12 | 13 | {% include 'base/navbar.html' with brand_name='eCommerce' %} 14 |
15 | {% if messages %} 16 |
17 | {% for message in messages %} 18 | 19 | {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}Important: {% endif %} 20 | {{ message }}
21 | 22 | {% endfor %} 23 |
24 | {% endif %} 25 | {% block content %}{% endblock %} 26 |
27 | 28 | {% include 'base/js.html' %} 29 | {% block javascript %} 30 | 31 | {% endblock %} 32 | 33 | -------------------------------------------------------------------------------- /src/templates/base/css.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/templates/base/forms.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block content %} 5 |
6 | {% if title %}

{{ title }}

{% endif %} 7 |
{% csrf_token %} 8 | {% if next_url %} 9 | 10 | {% endif %} 11 | {{ form.as_p }} 12 | 13 |
14 |
15 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/base/js.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% include 'base/js_templates.html' %} 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/templates/base/js_templates.html: -------------------------------------------------------------------------------- 1 | {% verbatim %} 2 | 3 | 22 | {% endverbatim %} -------------------------------------------------------------------------------- /src/templates/base/navbar.html: -------------------------------------------------------------------------------- 1 | {% url 'home' as home_url %} 2 | {% url 'contact' as contact_url %} 3 | {% url 'products:list' as product_list_url %} 4 | {% url 'login' as login_url %} 5 | {% url 'logout' as logout_url %} 6 | {% url 'register' as register_url %} 7 | 8 | {% url 'account:home' as account_url %} 9 | {% url 'cart:home' as cart_url %} 10 | 11 | -------------------------------------------------------------------------------- /src/templates/contact/view.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block content %} 5 |
6 |

{{ title }}

7 |

Hello, world we're working!

8 |
9 |
10 |
11 |
12 | 13 |
14 |

{{ content }}

15 |
{% csrf_token %} 16 | {{ form.as_p }} 17 | 18 |
19 | 20 | 21 | 27 |
28 |
29 |
30 |
31 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/home_page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | 4 | Home Page Template 5 | 6 | {% block base_head %} 7 | 8 | {% endblock %} 9 | 10 | {% block content %} 11 |
12 |

{{ title }}

13 |

Hello, world we're working!

14 |
15 | 16 |
17 |
18 | 19 |

{{ content }}

20 |
21 |
22 | {% if request.user.is_authenticated %} 23 |
24 |
25 |

Premium

26 | {{ premium_content }} 27 |
28 |
29 | {% endif %} 30 | 31 | 32 | {% endblock %} 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 |

{{ title }}

48 |

Hello, world we're working!

49 |
50 |
51 |
52 |
53 | 54 |

{{ content }}

55 |
56 |
57 | {% if request.user.is_authenticated %} 58 |
59 |
60 |

Premium

61 | {{ premium_content }} 62 |
63 |
64 | {% endif %} 65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/templates/registration/activation-error.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
4 |
5 | {% if key %} 6 |

Activation Error. Please try again.

7 | {% else %} 8 |

Re-activate your Email Below.

9 | {% endif %} 10 |
11 |
12 |
{% csrf_token %} 13 | {{ form.as_p }} 14 | 15 |
16 |
17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /src/templates/registration/emails/verify.html: -------------------------------------------------------------------------------- 1 |

Hello,

2 | 3 |

Please activate your account for {{ email }} by clicking the link below:

4 | 5 |

{{ path }}

6 |

Once you activate, you can login!

7 |

Thank you,

8 |

Python eCommerce

9 | -------------------------------------------------------------------------------- /src/templates/registration/emails/verify.txt: -------------------------------------------------------------------------------- 1 | Hello, 2 | 3 | Please activate your account for {{ email }} by clicking the link below: 4 | 5 | 6 | 7 | {{ path }} 8 | 9 | 10 | 11 | Once you activate, you can login! 12 | 13 | Thank you, 14 | 15 | Python eCommerce -------------------------------------------------------------------------------- /src/templates/registration/password_change_done.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
4 |
5 |

Password successfully changed!

6 |
7 |
8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /src/templates/registration/password_change_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
4 |
5 |

Change your Password

6 |
{% csrf_token %} 7 | {{ form.as_p }} 8 | 9 | 10 |
11 |
12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /src/templates/registration/password_reset_complete.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
4 |
5 |

Password reset complete

6 | Login 7 |
8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /src/templates/registration/password_reset_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
4 |
5 |

Set your Password

6 |
{% csrf_token %} 7 | {{ form.as_p }} 8 | 9 | 10 |
11 |
12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /src/templates/registration/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
4 |
5 |

Rest Instructions Sent

6 |

Please check your email

7 |
8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /src/templates/registration/password_reset_email.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans %} 3 | Hello, 4 | 5 | Reset your password on {{ domain }} for {{ user }}: 6 | {% endblocktrans %} 7 | 8 | {% block reset_link %} 9 | {{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} 10 | 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /src/templates/registration/password_reset_email.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans %} 3 | Hello, 4 | 5 | Reset your password on {{ domain }} for {{ user }}: 6 | {% endblocktrans %} 7 | 8 | {% block reset_link %} 9 | {{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} 10 | 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /src/templates/registration/password_reset_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
4 |
5 |

Reset your Password

6 |
{% csrf_token %} 7 | {{ form.as_p }} 8 | 9 | 10 |
11 |
12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /static_cdn/media_root/products/1130260399/1130260399.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/static_cdn/media_root/products/1130260399/1130260399.png -------------------------------------------------------------------------------- /static_cdn/media_root/products/1916522416/1916522416.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/static_cdn/media_root/products/1916522416/1916522416.png -------------------------------------------------------------------------------- /static_cdn/media_root/products/2473283945/2473283945.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": "." 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /static_cdn/media_root/products/2969889474/2969889474.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/static_cdn/media_root/products/2969889474/2969889474.jpg -------------------------------------------------------------------------------- /static_cdn/media_root/products/470776353/470776353.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/static_cdn/media_root/products/470776353/470776353.png -------------------------------------------------------------------------------- /static_cdn/media_root/products/Screen_Shot_2017-09-01_at_2.48.40_PM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/static_cdn/media_root/products/Screen_Shot_2017-09-01_at_2.48.40_PM.png -------------------------------------------------------------------------------- /static_cdn/protected_media/product/my-awesome-album/basic_audio.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/static_cdn/protected_media/product/my-awesome-album/basic_audio.m4a -------------------------------------------------------------------------------- /static_cdn/protected_media/product/my-awesome-album/basic_audio_5W1qjNh.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/static_cdn/protected_media/product/my-awesome-album/basic_audio_5W1qjNh.m4a -------------------------------------------------------------------------------- /static_cdn/protected_media/product/my-awesome-album/basic_audio_5W1qjNh_Ntvw9l5.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/static_cdn/protected_media/product/my-awesome-album/basic_audio_5W1qjNh_Ntvw9l5.m4a -------------------------------------------------------------------------------- /static_cdn/protected_media/product/my-awesome-album/basic_audio_DwLL00o.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/static_cdn/protected_media/product/my-awesome-album/basic_audio_DwLL00o.m4a -------------------------------------------------------------------------------- /static_cdn/protected_media/product/my-awesome-album/basic_audio_uuyWQIO.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/static_cdn/protected_media/product/my-awesome-album/basic_audio_uuyWQIO.m4a -------------------------------------------------------------------------------- /static_cdn/protected_media/product/my-awesome-album/basic_audio_uuyWQIO_soaLtH9.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/static_cdn/protected_media/product/my-awesome-album/basic_audio_uuyWQIO_soaLtH9.m4a -------------------------------------------------------------------------------- /static_cdn/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 | overflow: hidden; 25 | text-overflow: ellipsis; 26 | -o-text-overflow: ellipsis; 27 | } 28 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/css/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto'; 3 | src: url('../fonts/Roboto-Bold-webfont.woff'); 4 | font-weight: 700; 5 | font-style: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: 'Roboto'; 10 | src: url('../fonts/Roboto-Regular-webfont.woff'); 11 | font-weight: 400; 12 | font-style: normal; 13 | } 14 | 15 | @font-face { 16 | font-family: 'Roboto'; 17 | src: url('../fonts/Roboto-Light-webfont.woff'); 18 | font-weight: 300; 19 | font-style: normal; 20 | } 21 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/css/login.css: -------------------------------------------------------------------------------- 1 | /* LOGIN FORM */ 2 | 3 | body.login { 4 | background: #f8f8f8; 5 | } 6 | 7 | .login #header { 8 | height: auto; 9 | padding: 5px 16px; 10 | } 11 | 12 | .login #header h1 { 13 | font-size: 18px; 14 | } 15 | 16 | .login #header h1 a { 17 | color: #fff; 18 | } 19 | 20 | .login #content { 21 | padding: 20px 20px 0; 22 | } 23 | 24 | .login #container { 25 | background: #fff; 26 | border: 1px solid #eaeaea; 27 | border-radius: 4px; 28 | overflow: hidden; 29 | width: 28em; 30 | min-width: 300px; 31 | margin: 100px auto; 32 | } 33 | 34 | .login #content-main { 35 | width: 100%; 36 | } 37 | 38 | .login .form-row { 39 | padding: 4px 0; 40 | float: left; 41 | width: 100%; 42 | border-bottom: none; 43 | } 44 | 45 | .login .form-row label { 46 | padding-right: 0.5em; 47 | line-height: 2em; 48 | font-size: 1em; 49 | clear: both; 50 | color: #333; 51 | } 52 | 53 | .login .form-row #id_username, .login .form-row #id_password { 54 | clear: both; 55 | padding: 8px; 56 | width: 100%; 57 | -webkit-box-sizing: border-box; 58 | -moz-box-sizing: border-box; 59 | box-sizing: border-box; 60 | } 61 | 62 | .login span.help { 63 | font-size: 10px; 64 | display: block; 65 | } 66 | 67 | .login .submit-row { 68 | clear: both; 69 | padding: 1em 0 0 9.4em; 70 | margin: 0; 71 | border: none; 72 | background: none; 73 | text-align: left; 74 | } 75 | 76 | .login .password-reset-link { 77 | text-align: center; 78 | } 79 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/fonts/README.txt: -------------------------------------------------------------------------------- 1 | Roboto webfont source: https://www.google.com/fonts/specimen/Roboto 2 | Weights used in this project: Light (300), Regular (400), Bold (700) 3 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/fonts/Roboto-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/static_cdn/static_root/admin/fonts/Roboto-Bold-webfont.woff -------------------------------------------------------------------------------- /static_cdn/static_root/admin/fonts/Roboto-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/static_cdn/static_root/admin/fonts/Roboto-Light-webfont.woff -------------------------------------------------------------------------------- /static_cdn/static_root/admin/fonts/Roboto-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/static_cdn/static_root/admin/fonts/Roboto-Regular-webfont.woff -------------------------------------------------------------------------------- /static_cdn/static_root/admin/img/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Code Charm Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/img/README.txt: -------------------------------------------------------------------------------- 1 | All icons are taken from Font Awesome (http://fontawesome.io/) project. 2 | The Font Awesome font is licensed under the SIL OFL 1.1: 3 | - http://scripts.sil.org/OFL 4 | 5 | SVG icons source: https://github.com/encharm/Font-Awesome-SVG-PNG 6 | Font-Awesome-SVG-PNG is licensed under the MIT license (see file license 7 | in current folder). 8 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/img/calendar-icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/img/gis/move_vertex_off.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/img/gis/move_vertex_on.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/img/icon-addlink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/img/icon-alert.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/img/icon-calendar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/img/icon-changelink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/img/icon-clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/img/icon-deletelink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/img/icon-no.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/img/icon-unknown-alt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/img/icon-unknown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/img/icon-yes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/img/inline-delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/img/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/img/selector-icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/img/sorting-icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/img/tooltag-add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/img/tooltag-arrowright.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/js/actions.min.js: -------------------------------------------------------------------------------- 1 | (function(a){var f;a.fn.actions=function(e){var b=a.extend({},a.fn.actions.defaults,e),g=a(this),k=!1,l=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()},n=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()},p=function(){n(); 2 | a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)},q=function(c){c?l():n();a(g).prop("checked",c).parent().parent().toggleClass(b.selectedClass,c)},h=function(){var c=a(g).filter(":checked").length,d=a(".action-counter").data("actionsIcnt");a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:d},!0));a(b.allToggle).prop("checked",function(){var a;c===g.length?(a=!0,l()):(a=!1,p());return a})};a(b.counterContainer).show(); 3 | a(this).filter(":checked").each(function(c){a(this).parent().parent().toggleClass(b.selectedClass);h();1===a(b.acrossInput).val()&&m()});a(b.allToggle).show().click(function(){q(a(this).prop("checked"));h()});a("a",b.acrossQuestions).click(function(c){c.preventDefault();a(b.acrossInput).val(1);m()});a("a",b.acrossClears).click(function(c){c.preventDefault();a(b.allToggle).prop("checked",!1);p();q(0);h()});f=null;a(g).click(function(c){c||(c=window.event);var d=c.target?c.target:c.srcElement;if(f&& 4 | a.data(f)!==a.data(d)&&!0===c.shiftKey){var e=!1;a(f).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked);a(g).each(function(){if(a.data(this)===a.data(f)||a.data(this)===a.data(d))e=e?!1:!0;e&&a(this).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);f=d;h()});a("form#changelist-form table#result_list tr").find("td:gt(0) :input").change(function(){k=!0});a('form#changelist-form button[name="index"]').click(function(a){if(k)return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."))}); 5 | a('form#changelist-form input[name="_save"]').click(function(c){var d=!1;a("select option:selected",b.actionContainer).each(function(){a(this).val()&&(d=!0)});if(d)return k?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.")):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."))})}; 6 | 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"};a(document).ready(function(){var e=a("tr input.action-select");0' + gettext("Show") + 11 | ')'); 12 | } 13 | }); 14 | // Add toggle to anchor tag 15 | $("fieldset.collapse a.collapse-toggle").click(function(ev) { 16 | if ($(this).closest("fieldset").hasClass("collapsed")) { 17 | // Show 18 | $(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset", [$(this).attr("id")]); 19 | } else { 20 | // Hide 21 | $(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", [$(this).attr("id")]); 22 | } 23 | return false; 24 | }); 25 | }); 26 | })(django.jQuery); 27 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/js/collapse.min.js: -------------------------------------------------------------------------------- 1 | (function(a){a(document).ready(function(){a("fieldset.collapse").each(function(b,c){0===a(c).find("div.errors").length&&a(c).addClass("collapsed").find("h2").first().append(' ('+gettext("Show")+")")});a("fieldset.collapse a.collapse-toggle").click(function(b){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_cdn/static_root/admin/js/jquery.init.js: -------------------------------------------------------------------------------- 1 | /*global django:true, jQuery:false*/ 2 | /* Puts the included jQuery into our own namespace using noConflict and passing 3 | * it 'true'. This ensures that the included jQuery doesn't pollute the global 4 | * namespace (i.e. this preserves pre-existing values for both window.$ and 5 | * window.jQuery). 6 | */ 7 | var django = django || {}; 8 | django.jQuery = jQuery.noConflict(true); 9 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/js/popup_response.js: -------------------------------------------------------------------------------- 1 | /*global opener */ 2 | (function() { 3 | 'use strict'; 4 | var initData = JSON.parse(document.getElementById('django-admin-popup-response-constants').dataset.popupResponse); 5 | switch(initData.action) { 6 | case 'change': 7 | opener.dismissChangeRelatedObjectPopup(window, initData.value, initData.obj, initData.new_value); 8 | break; 9 | case 'delete': 10 | opener.dismissDeleteRelatedObjectPopup(window, initData.value); 11 | break; 12 | default: 13 | opener.dismissAddRelatedObjectPopup(window, initData.value, initData.obj); 14 | break; 15 | } 16 | })(); 17 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/js/prepopulate.js: -------------------------------------------------------------------------------- 1 | /*global URLify*/ 2 | (function($) { 3 | 'use strict'; 4 | $.fn.prepopulate = function(dependencies, maxLength, allowUnicode) { 5 | /* 6 | Depends on urlify.js 7 | Populates a selected field with the values of the dependent fields, 8 | URLifies and shortens the string. 9 | dependencies - array of dependent fields ids 10 | maxLength - maximum length of the URLify'd string 11 | allowUnicode - Unicode support of the URLify'd string 12 | */ 13 | return this.each(function() { 14 | var prepopulatedField = $(this); 15 | 16 | var populate = function() { 17 | // Bail if the field's value has been changed by the user 18 | if (prepopulatedField.data('_changed')) { 19 | return; 20 | } 21 | 22 | var values = []; 23 | $.each(dependencies, function(i, field) { 24 | field = $(field); 25 | if (field.val().length > 0) { 26 | values.push(field.val()); 27 | } 28 | }); 29 | prepopulatedField.val(URLify(values.join(' '), maxLength, allowUnicode)); 30 | }; 31 | 32 | prepopulatedField.data('_changed', false); 33 | prepopulatedField.change(function() { 34 | prepopulatedField.data('_changed', true); 35 | }); 36 | 37 | if (!prepopulatedField.val()) { 38 | $(dependencies.join(',')).keyup(populate).change(populate).focus(populate); 39 | } 40 | }); 41 | }; 42 | })(django.jQuery); 43 | -------------------------------------------------------------------------------- /static_cdn/static_root/admin/js/prepopulate.min.js: -------------------------------------------------------------------------------- 1 | (function(c){c.fn.prepopulate=function(e,f,g){return this.each(function(){var a=c(this),b=function(){if(!a.data("_changed")){var b=[];c.each(e,function(a,d){d=c(d);0 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /static_cdn/static_root/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #ccc; 3 | } -------------------------------------------------------------------------------- /static_cdn/static_root/css/stripe-custom-style.css: -------------------------------------------------------------------------------- 1 | /** 2 | * The CSS shown here will not be introduced in the Quickstart guide, but shows 3 | * how you can use CSS to style your Element's container. 4 | */ 5 | .StripeElement { 6 | background-color: white; 7 | padding: 8px 12px; 8 | border-radius: 4px; 9 | border: 1px solid transparent; 10 | box-shadow: 0 1px 3px 0 #e6ebf1; 11 | -webkit-transition: box-shadow 150ms ease; 12 | transition: box-shadow 150ms ease; 13 | } 14 | 15 | .StripeElement--focus { 16 | box-shadow: 0 1px 3px 0 #cfd7df; 17 | } 18 | 19 | .StripeElement--invalid { 20 | border-color: #fa755a; 21 | } 22 | 23 | .StripeElement--webkit-autofill { 24 | background-color: #fefde5 !important; 25 | } 26 | -------------------------------------------------------------------------------- /static_cdn/static_root/img/beach.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/eCommerce/80e38cb5319c8f470db00c469bebd7a5e9cdad38/static_cdn/static_root/img/beach.jpg -------------------------------------------------------------------------------- /static_cdn/static_root/js/csrf.ajax.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | // using jQuery 3 | function getCookie(name) { 4 | var cookieValue = null; 5 | if (document.cookie && document.cookie !== '') { 6 | var cookies = document.cookie.split(';'); 7 | for (var i = 0; i < cookies.length; i++) { 8 | var cookie = jQuery.trim(cookies[i]); 9 | // Does this cookie string begin with the name we want? 10 | if (cookie.substring(0, name.length + 1) === (name + '=')) { 11 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 12 | break; 13 | } 14 | } 15 | } 16 | return cookieValue; 17 | } 18 | var csrftoken = getCookie('csrftoken'); 19 | 20 | function csrfSafeMethod(method) { 21 | // these HTTP methods do not require CSRF protection 22 | return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 23 | } 24 | $.ajaxSetup({ 25 | beforeSend: function(xhr, settings) { 26 | if (!csrfSafeMethod(settings.type) && !this.crossDomain) { 27 | xhr.setRequestHeader("X-CSRFToken", csrftoken); 28 | } 29 | } 30 | }); 31 | 32 | }) --------------------------------------------------------------------------------