20 |
21 |
Billing to
22 |
23 | {{ order.billing_address.get_address }}
24 |
25 |
Shipping to
26 |
27 | {{ order.shipping_address.get_address }}
28 |
--------------------------------------------------------------------------------
/src/newsletter/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 |
3 | from .models import SignUp
4 |
5 | class ContactForm(forms.Form):
6 | full_name = forms.CharField(required=False)
7 | email = forms.EmailField()
8 | message = forms.CharField()
9 |
10 |
11 | class SignUpForm(forms.ModelForm):
12 | class Meta:
13 | model = SignUp
14 | fields = ['full_name', 'email']
15 | ### exclude = ['full_name']
16 |
17 | def clean_email(self):
18 | email = self.cleaned_data.get('email')
19 | email_base, provider = email.split("@")
20 | domain, extension = provider.split('.')
21 | # if not domain == 'USC':
22 | # raise forms.ValidationError("Please make sure you use your USC email.")
23 | if not extension == "edu":
24 | raise forms.ValidationError("Please use a valid .EDU email address")
25 | return email
26 |
27 | def clean_full_name(self):
28 | full_name = self.cleaned_data.get('full_name')
29 | #write validation code.
30 | return full_name
--------------------------------------------------------------------------------
/src/carts/migrations/0008_auto_20150827_2153.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('carts', '0007_cart_subtotal'),
11 | ]
12 |
13 | operations = [
14 | migrations.AddField(
15 | model_name='cart',
16 | name='tax_total',
17 | field=models.DecimalField(default=25.0, max_digits=50, decimal_places=2),
18 | ),
19 | migrations.AddField(
20 | model_name='cart',
21 | name='total',
22 | field=models.DecimalField(default=25.0, max_digits=50, decimal_places=2),
23 | ),
24 | migrations.AlterField(
25 | model_name='cart',
26 | name='subtotal',
27 | field=models.DecimalField(default=25.0, max_digits=50, decimal_places=2),
28 | ),
29 | ]
30 |
--------------------------------------------------------------------------------
/static_in_env/static_root/admin/js/related-widget-wrapper.js:
--------------------------------------------------------------------------------
1 | django.jQuery(function($){
2 | function updateLinks() {
3 | var $this = $(this);
4 | var siblings = $this.nextAll('.change-related, .delete-related');
5 | if (!siblings.length) return;
6 | var value = $this.val();
7 | if (value) {
8 | siblings.each(function(){
9 | var elm = $(this);
10 | elm.attr('href', elm.attr('data-href-template').replace('__fk__', value));
11 | });
12 | } else siblings.removeAttr('href');
13 | }
14 | var container = $(document);
15 | container.on('change', '.related-widget-wrapper select', updateLinks);
16 | container.find('.related-widget-wrapper select').each(updateLinks);
17 | container.on('click', '.related-widget-wrapper-link', function(event){
18 | if (this.href) {
19 | showRelatedObjectPopup(this);
20 | }
21 | event.preventDefault();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/orders/migrations/0005_auto_20150828_2342.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('orders', '0004_order'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='order',
16 | name='billing_address',
17 | field=models.ForeignKey(related_name='billing_address', to='orders.UserAddress', null=True),
18 | ),
19 | migrations.AlterField(
20 | model_name='order',
21 | name='shipping_address',
22 | field=models.ForeignKey(related_name='shipping_address', to='orders.UserAddress', null=True),
23 | ),
24 | migrations.AlterField(
25 | model_name='order',
26 | name='user',
27 | field=models.ForeignKey(to='orders.UserCheckout', null=True),
28 | ),
29 | ]
30 |
--------------------------------------------------------------------------------
/src/orders/migrations/0003_useraddress.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('orders', '0002_auto_20150828_2151'),
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='UserAddress',
16 | fields=[
17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
18 | ('type', models.CharField(max_length=120, choices=[(b'billing', b'Billing'), (b'shipping', b'Shipping')])),
19 | ('street', models.CharField(max_length=120)),
20 | ('city', models.CharField(max_length=120)),
21 | ('state', models.CharField(max_length=120)),
22 | ('zipcode', models.CharField(max_length=120)),
23 | ('user', models.ForeignKey(to='orders.UserCheckout')),
24 | ],
25 | ),
26 | ]
27 |
--------------------------------------------------------------------------------
/static_in_env/static_root/admin/js/collapse.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 | $(document).ready(function() {
3 | // Add anchor tag for Show/Hide link
4 | $("fieldset.collapse").each(function(i, elem) {
5 | // Don't hide if fields in this fieldset have errors
6 | if ($(elem).find("div.errors").length == 0) {
7 | $(elem).addClass("collapsed").find("h2").first().append(' (' + gettext("Show") +
9 | ' )');
10 | }
11 | });
12 | // Add toggle to anchor tag
13 | $("fieldset.collapse a.collapse-toggle").click(function(ev) {
14 | if ($(this).closest("fieldset").hasClass("collapsed")) {
15 | // Show
16 | $(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset", [$(this).attr("id")]);
17 | } else {
18 | // Hide
19 | $(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", [$(this).attr("id")]);
20 | }
21 | return false;
22 | });
23 | });
24 | })(django.jQuery);
25 |
--------------------------------------------------------------------------------
/src/products/migrations/0002_variation.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('products', '0001_initial'),
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='Variation',
16 | fields=[
17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
18 | ('title', models.CharField(max_length=120)),
19 | ('price', models.DecimalField(max_digits=20, decimal_places=2)),
20 | ('sale_price', models.DecimalField(null=True, max_digits=20, decimal_places=2, blank=True)),
21 | ('active', models.BooleanField(default=True)),
22 | ('inventory', models.IntegerField(null=True, blank=True)),
23 | ('product', models.ForeignKey(to='products.Product')),
24 | ],
25 | ),
26 | ]
27 |
--------------------------------------------------------------------------------
/src/products/mixins.py:
--------------------------------------------------------------------------------
1 | from django.contrib.admin.views.decorators import staff_member_required
2 | from django.contrib.auth.decorators import login_required
3 | from django.utils.decorators import method_decorator
4 |
5 | from django.http import Http404
6 |
7 |
8 | class StaffRequiredMixin(object):
9 | @classmethod
10 | def as_view(self, *args, **kwargs):
11 | view = super(StaffRequiredMixin, self).as_view(*args, **kwargs)
12 | return login_required(view)
13 |
14 | @method_decorator(login_required)
15 | def dispatch(self, request, *args, **kwargs):
16 | if request.user.is_staff:
17 | return super(StaffRequiredMixin, self).dispatch(request, *args, **kwargs)
18 | else:
19 | raise Http404
20 |
21 |
22 |
23 | class LoginRequiredMixin(object):
24 | @classmethod
25 | def as_view(self, *args, **kwargs):
26 | view = super(LoginRequiredMixin, self).as_view(*args, **kwargs)
27 | return login_required(view)
28 |
29 | @method_decorator(login_required)
30 | def dispatch(self, request, *args, **kwargs):
31 | return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs)
32 |
--------------------------------------------------------------------------------
/src/orders/mixins.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.decorators import login_required
2 | from django.utils.decorators import method_decorator
3 |
4 | from carts.models import Cart
5 | from .models import Order
6 |
7 |
8 | class LoginRequiredMixin(object):
9 | @method_decorator(login_required)
10 | def dispatch(self, request, *args, **kwargs):
11 | return super(LoginRequiredMixin, self).dispatch(request,*args, **kwargs)
12 |
13 |
14 |
15 | class CartOrderMixin(object):
16 | def get_order(self, *args, **kwargs):
17 | cart = self.get_cart()
18 | if cart is None:
19 | return None
20 | new_order_id = self.request.session.get("order_id")
21 | if new_order_id is None:
22 | new_order = Order.objects.create(cart=cart)
23 | self.request.session["order_id"] = new_order.id
24 | else:
25 | new_order = Order.objects.get(id=new_order_id)
26 | return new_order
27 |
28 | def get_cart(self, *args, **kwargs):
29 | cart_id = self.request.session.get("cart_id")
30 | if cart_id == None:
31 | return None
32 | cart = Cart.objects.get(id=cart_id)
33 | if cart.items.count() <= 0:
34 | return None
35 | return cart
--------------------------------------------------------------------------------
/src/carts/migrations/0010_auto_20150922_2231.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('carts', '0009_cart_tax_percentage'),
11 | ]
12 |
13 | operations = [
14 | migrations.AddField(
15 | model_name='cart',
16 | name='active',
17 | field=models.BooleanField(default=True),
18 | ),
19 | migrations.AlterField(
20 | model_name='cart',
21 | name='subtotal',
22 | field=models.DecimalField(default=0.0, max_digits=50, decimal_places=2),
23 | ),
24 | migrations.AlterField(
25 | model_name='cart',
26 | name='tax_total',
27 | field=models.DecimalField(default=0.0, max_digits=50, decimal_places=2),
28 | ),
29 | migrations.AlterField(
30 | model_name='cart',
31 | name='total',
32 | field=models.DecimalField(default=0.0, max_digits=50, decimal_places=2),
33 | ),
34 | ]
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Coding For Entrepreneurs
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/src/products/migrations/0006_productfeatured.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 | import products.models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('products', '0005_auto_20150820_2238'),
12 | ]
13 |
14 | operations = [
15 | migrations.CreateModel(
16 | name='ProductFeatured',
17 | fields=[
18 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
19 | ('image', models.ImageField(upload_to=products.models.image_upload_to_featured)),
20 | ('title', models.CharField(max_length=120, null=True, blank=True)),
21 | ('text', models.CharField(max_length=220, null=True, blank=True)),
22 | ('text_right', models.BooleanField(default=False)),
23 | ('show_price', models.BooleanField(default=False)),
24 | ('active', models.BooleanField(default=True)),
25 | ('product', models.ForeignKey(to='products.Product')),
26 | ],
27 | ),
28 | ]
29 |
--------------------------------------------------------------------------------
/static_in_env/static_root/admin/js/LICENSE-JQUERY.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010 John Resig, http://jquery.com/
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/src/orders/migrations/0004_order.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('carts', '0009_cart_tax_percentage'),
11 | ('orders', '0003_useraddress'),
12 | ]
13 |
14 | operations = [
15 | migrations.CreateModel(
16 | name='Order',
17 | fields=[
18 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
19 | ('shipping_total_price', models.DecimalField(default=5.99, max_digits=50, decimal_places=2)),
20 | ('order_total', models.DecimalField(max_digits=50, decimal_places=2)),
21 | ('billing_address', models.ForeignKey(related_name='billing_address', to='orders.UserAddress')),
22 | ('cart', models.ForeignKey(to='carts.Cart')),
23 | ('shipping_address', models.ForeignKey(related_name='shipping_address', to='orders.UserAddress')),
24 | ('user', models.ForeignKey(to='orders.UserCheckout')),
25 | ],
26 | ),
27 | ]
28 |
--------------------------------------------------------------------------------
/src/products/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 |
3 | from django.forms.models import modelformset_factory
4 |
5 |
6 | from .models import Variation, Category
7 |
8 |
9 | CAT_CHOICES = (
10 | ('electronics', 'Electronics'),
11 | ('accessories', 'Accessories'),
12 | )
13 |
14 | class ProductFilterForm(forms.Form):
15 | q = forms.CharField(label='Search', required=False)
16 | category_id = forms.ModelMultipleChoiceField(
17 | label='Category',
18 | queryset=Category.objects.all(),
19 | widget=forms.CheckboxSelectMultiple,
20 | required=False)
21 | # category_title = forms.ChoiceField(
22 | # label='Category',
23 | # choices=CAT_CHOICES,
24 | # widget=forms.CheckboxSelectMultiple,
25 | # required=False)
26 | max_price = forms.DecimalField(decimal_places=2, max_digits=12, required=False)
27 | min_price = forms.DecimalField(decimal_places=2, max_digits=12, required=False)
28 |
29 |
30 |
31 | class VariationInventoryForm(forms.ModelForm):
32 | class Meta:
33 | model = Variation
34 | fields = [
35 | "price",
36 | "sale_price",
37 | "inventory",
38 | "active",
39 | ]
40 |
41 |
42 | VariationInventoryFormSet = modelformset_factory(Variation, form=VariationInventoryForm, extra=0)
--------------------------------------------------------------------------------
/static_in_env/static_root/admin/css/login.css:
--------------------------------------------------------------------------------
1 | /* LOGIN FORM */
2 |
3 | body.login {
4 | background: #eee;
5 | }
6 |
7 | .login #container {
8 | background: white;
9 | border: 1px solid #ccc;
10 | width: 28em;
11 | min-width: 300px;
12 | margin-left: auto;
13 | margin-right: auto;
14 | margin-top: 100px;
15 | }
16 |
17 | .login #content-main {
18 | width: 100%;
19 | }
20 |
21 | .login form {
22 | margin-top: 1em;
23 | }
24 |
25 | .login .form-row {
26 | padding: 4px 0;
27 | float: left;
28 | width: 100%;
29 | }
30 |
31 | .login .form-row label {
32 | padding-right: 0.5em;
33 | line-height: 2em;
34 | font-size: 1em;
35 | clear: both;
36 | color: #333;
37 | }
38 |
39 | .login .form-row #id_username, .login .form-row #id_password {
40 | clear: both;
41 | padding: 6px;
42 | width: 100%;
43 | -webkit-box-sizing: border-box;
44 | -moz-box-sizing: border-box;
45 | box-sizing: border-box;
46 | }
47 |
48 | .login span.help {
49 | font-size: 10px;
50 | display: block;
51 | }
52 |
53 | .login .submit-row {
54 | clear: both;
55 | padding: 1em 0 0 9.4em;
56 | }
57 |
58 | .login .password-reset-link {
59 | text-align: center;
60 | }
61 |
--------------------------------------------------------------------------------
/src/templates/head_css.html:
--------------------------------------------------------------------------------
1 |
2 | {% load staticfiles %}
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
17 |
18 |
22 |
--------------------------------------------------------------------------------
/src/templates/javascript.html:
--------------------------------------------------------------------------------
1 | {% load staticfiles %}
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/templates/example_fluid.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block jumbotron %}
4 | _
5 | {% endblock %}
6 |
7 | {% block content %}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | .col-md-4
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
.col-md-8
25 |
26 |
27 |
28 |
29 |
30 |
31 | .col-md-4
32 |
33 |
34 |
35 |
36 |
.col-md-4
37 |
38 |
39 |
40 |
41 |
.col-md-4
42 |
43 |
47 |
48 |
49 |
50 |
59 |
60 | {% endblock %}
--------------------------------------------------------------------------------
/static_in_env/static_root/admin/css/ie.css:
--------------------------------------------------------------------------------
1 | /* IE 6 & 7 */
2 |
3 | /* Proper fixed width for dashboard in IE6 */
4 |
5 | .dashboard #content {
6 | *width: 768px;
7 | }
8 |
9 | .dashboard #content-main {
10 | *width: 535px;
11 | }
12 |
13 | /* IE 6 ONLY */
14 |
15 | /* Keep header from flowing off the page */
16 |
17 | #container {
18 | _position: static;
19 | }
20 |
21 | /* Put the right sidebars back on the page */
22 |
23 | .colMS #content-related {
24 | _margin-right: 0;
25 | _margin-left: 10px;
26 | _position: static;
27 | }
28 |
29 | /* Put the left sidebars back on the page */
30 |
31 | .colSM #content-related {
32 | _margin-right: 10px;
33 | _margin-left: -115px;
34 | _position: static;
35 | }
36 |
37 | .form-row {
38 | _height: 1%;
39 | }
40 |
41 | /* Fix right margin for changelist filters in IE6 */
42 |
43 | #changelist-filter ul {
44 | _margin-right: -10px;
45 | }
46 |
47 | /* IE ignores min-height, but treats height as if it were min-height */
48 |
49 | .change-list .filtered {
50 | _height: 400px;
51 | }
52 |
53 | /* IE doesn't know alpha transparency in PNGs */
54 |
55 | .inline-deletelink {
56 | background: transparent url(../img/inline-delete-8bit.png) no-repeat;
57 | }
58 |
59 | /* IE7 doesn't support inline-block */
60 | .change-list ul.toplinks li {
61 | zoom: 1;
62 | *display: inline;
63 | }
64 |
--------------------------------------------------------------------------------
/src/orders/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from django.contrib.auth import get_user_model
3 |
4 | from .models import UserAddress
5 | User = get_user_model()
6 |
7 | class GuestCheckoutForm(forms.Form):
8 | email = forms.EmailField()
9 | email2 = forms.EmailField(label='Verify Email')
10 |
11 | def clean_email2(self):
12 | email = self.cleaned_data.get("email")
13 | email2 = self.cleaned_data.get("email2")
14 |
15 | if email == email2:
16 | user_exists = User.objects.filter(email=email).count()
17 | if user_exists != 0:
18 | raise forms.ValidationError("This User already exists. Please login instead.")
19 | return email2
20 | else:
21 | raise forms.ValidationError("Please confirm emails are the same")
22 |
23 |
24 |
25 |
26 | class AddressForm(forms.Form):
27 | billing_address = forms.ModelChoiceField(
28 | queryset=UserAddress.objects.filter(type="billing"),
29 | widget = forms.RadioSelect,
30 | empty_label = None
31 | )
32 | shipping_address = forms.ModelChoiceField(
33 | queryset=UserAddress.objects.filter(type="shipping"),
34 | widget = forms.RadioSelect,
35 | empty_label = None,
36 |
37 | )
38 |
39 |
40 |
41 | class UserAddressForm(forms.ModelForm):
42 | class Meta:
43 | model = UserAddress
44 | fields = [
45 | 'street',
46 | 'city',
47 | 'state',
48 | 'zipcode',
49 | 'type'
50 | ]
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/webfaction/httpd.conf:
--------------------------------------------------------------------------------
1 | ServerRoot "/home/cfedeploy/webapps/mvpland/apache2"
2 |
3 | LoadModule authz_core_module modules/mod_authz_core.so
4 | LoadModule dir_module modules/mod_dir.so
5 | LoadModule env_module modules/mod_env.so
6 | LoadModule log_config_module modules/mod_log_config.so
7 | LoadModule mime_module modules/mod_mime.so
8 | LoadModule rewrite_module modules/mod_rewrite.so
9 | LoadModule setenvif_module modules/mod_setenvif.so
10 | LoadModule wsgi_module modules/mod_wsgi.so
11 | LoadModule unixd_module modules/mod_unixd.so
12 |
13 | LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
14 | CustomLog /home/cfedeploy/logs/user/access_mvpland.log combined
15 | ErrorLog /home/cfedeploy/logs/user/error_mvpland.log
16 |
17 | Listen 14383
18 | KeepAlive Off
19 | SetEnvIf X-Forwarded-SSL on HTTPS=1
20 | ServerLimit 1
21 | StartServers 1
22 | MaxRequestWorkers 5
23 | MinSpareThreads 1
24 | MaxSpareThreads 3
25 | ThreadsPerChild 5
26 |
27 | WSGIPythonPath /home/cfedeploy/webapps/mvpland:/home/cfedeploy/webapps/mvpland/src:/home/cfedeploy/webapps/mvpland/lib/python2.7
28 | WSGIDaemonProcess mvpland processes=2 threads=12 python-path=/home/cfedeploy/webapps/mvpland:/home/cfedeploy/webapps/mvpland/src:/home/cfedeploy/webapps/mvpland/lib/python2.7
29 | WSGIProcessGroup mvpland
30 | WSGIRestrictEmbedded On
31 | WSGILazyInitialization On
32 | WSGIScriptAlias / /home/cfedeploy/webapps/mvpland/src/trydjango18/wsgi.py
33 |
--------------------------------------------------------------------------------
/static_in_env/static_root/admin/js/prepopulate.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 | $.fn.prepopulate = function(dependencies, maxLength) {
3 | /*
4 | Depends on urlify.js
5 | Populates a selected field with the values of the dependent fields,
6 | URLifies and shortens the string.
7 | dependencies - array of dependent fields ids
8 | maxLength - maximum length of the URLify'd string
9 | */
10 | return this.each(function() {
11 | var prepopulatedField = $(this);
12 |
13 | var populate = function () {
14 | // Bail if the field's value has been changed by the user
15 | if (prepopulatedField.data('_changed')) {
16 | return;
17 | }
18 |
19 | var values = [];
20 | $.each(dependencies, function(i, field) {
21 | field = $(field);
22 | if (field.val().length > 0) {
23 | values.push(field.val());
24 | }
25 | });
26 | prepopulatedField.val(URLify(values.join(' '), maxLength));
27 | };
28 |
29 | prepopulatedField.data('_changed', false);
30 | prepopulatedField.change(function() {
31 | prepopulatedField.data('_changed', true);
32 | });
33 |
34 | if (!prepopulatedField.val()) {
35 | $(dependencies.join(',')).keyup(populate).change(populate).focus(populate);
36 | }
37 | });
38 | };
39 | })(django.jQuery);
40 |
--------------------------------------------------------------------------------
/src/carts/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 | from django.conf import settings
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12 | ('products', '0008_productfeatured_text_css_color'),
13 | ]
14 |
15 | operations = [
16 | migrations.CreateModel(
17 | name='Cart',
18 | fields=[
19 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
20 | ('timestamp', models.DateTimeField(auto_now_add=True)),
21 | ('updated', models.DateTimeField(auto_now=True)),
22 | ],
23 | ),
24 | migrations.CreateModel(
25 | name='CartItem',
26 | fields=[
27 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
28 | ('quantity', models.PositiveIntegerField(default=1)),
29 | ('item', models.ForeignKey(to='products.Variation')),
30 | ],
31 | ),
32 | migrations.AddField(
33 | model_name='cart',
34 | name='items',
35 | field=models.ManyToManyField(to='carts.CartItem'),
36 | ),
37 | migrations.AddField(
38 | model_name='cart',
39 | name='user',
40 | field=models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True),
41 | ),
42 | ]
43 |
--------------------------------------------------------------------------------
/src/products/migrations/0004_auto_20150820_2156.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 | import products.models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('products', '0003_productimage'),
12 | ]
13 |
14 | operations = [
15 | migrations.CreateModel(
16 | name='Category',
17 | fields=[
18 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
19 | ('title', models.CharField(unique=True, max_length=120)),
20 | ('slug', models.SlugField(unique=True)),
21 | ('description', models.TextField(null=True, blank=True)),
22 | ('active', models.BooleanField(default=True)),
23 | ('timestamp', models.DateTimeField(auto_now_add=True)),
24 | ],
25 | ),
26 | migrations.AlterField(
27 | model_name='productimage',
28 | name='image',
29 | field=models.ImageField(upload_to=products.models.image_upload_to),
30 | ),
31 | migrations.AddField(
32 | model_name='product',
33 | name='categories',
34 | field=models.ManyToManyField(to='products.Category', blank=True),
35 | ),
36 | migrations.AddField(
37 | model_name='product',
38 | name='default',
39 | field=models.ForeignKey(related_name='default_category', blank=True, to='products.Category', null=True),
40 | ),
41 | ]
42 |
--------------------------------------------------------------------------------
/walkthrough.md:
--------------------------------------------------------------------------------
1 | ## eCommerce 2 API Walkthrough
2 | An introduction to the eCommerce 2 API course.
3 |
4 |
5 |
6 | #### 1. Clone/Download the REPO
7 |
8 | [Download archive](https://github.com/codingforentrepreneurs/ecommerce-2-api/archive/master.zip)
9 |
10 | Clone with Git
11 | ```
12 | mkdir ecommerce-2-api-master && cd ecommerce-2-api-master
13 | git clone https://github.com/codingforentrepreneurs/ecommerce-2-api.git .
14 |
15 | ```
16 |
17 |
18 | #### 2. Create & Activate Virtualenv
19 | Assuming you are in the `ecommerce-2-api-master` directory created above
20 |
21 | ```
22 | virtualenv .
23 | ```
24 |
25 |
26 | Activate Virtual Environment
27 |
28 | Mac/Linux: ```source bin/activate```
29 |
30 | Windows: ```.\Scripts\activate```
31 |
32 |
33 | #### 3. Install Requirements
34 |
35 | ```
36 | (ecommerce-2-api-master)$ cd src
37 | (ecommerce-2-api-master)$ pip install -r requirements.txt
38 |
39 | ```
40 |
41 |
42 |
43 | #### 4. Run Local Server
44 |
45 | ```
46 | (ecommerce-2-api-master)$ python manage.py runserver
47 |
48 | ```
49 |
50 |
51 | #### 5. Open API url:
52 |
53 | [API URL @ http://127.0.0.1:8000/api/](http://http://127.0.0.1:8000/api/)
54 |
55 |
56 | #### 5. Run API Tests:
57 |
58 | Open new Terminal/Command Prompt and keep the above server running
59 | ```
60 | cd /path/to/your/ecommerce-2-api-master
61 | ```
62 |
63 | Activate Virtual Environment
64 |
65 | Mac/Linux: ```source bin/activate```
66 |
67 | Windows: ```.\Scripts\activate```
68 |
69 |
70 | ```
71 | (ecommerce-2-api-master)$ cd src/ecommerce2
72 | (ecommerce-2-api-master)$ python api_tests.py
73 |
74 | {"order_token":"eydvcmRlcl9pZCc6IDU4LCAndXNlcl9jaGVja291dF9pZCc6IDExfQ=="}
75 | ```
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/templates/base.html:
--------------------------------------------------------------------------------
1 | {% load staticfiles %}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
{% block head_title %}eCommerce 2{% endblock %}
15 |
16 | {% include 'head_css.html' %}
17 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | {% include 'navbar.html' %}
28 |
29 | {% if messages %}
30 |
31 |
32 |
33 |
×
34 | {% for message in messages %}
35 |
{{ message }}
36 | {% endfor %}
37 |
38 |
39 |
40 | {% endif %}
41 |
42 | {% block jumbotron %}
43 |
44 | {% endblock %}
45 |
46 |
47 |
48 |
49 | {% block content %}
50 |
51 | {% endblock %}
52 |
53 |
54 |
55 |
56 | {% include "javascript.html" %}
57 |
58 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/carts/templates/carts/checkout_view.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% load i18n %}
3 | {% load crispy_forms_tags %}
4 |
5 |
6 |
7 |
8 |
9 |
21 |
22 | {% block content %}
23 |
24 |
25 |
26 |
27 | {% if not user_can_continue %}
28 |
29 |
Continue as Guest
30 |
31 |
32 |
33 |
37 |
38 |
39 |
40 |
55 |
56 | {% else %}
57 |
58 |
59 |
60 | {% include "orders/order_summary_short.html" with order=order %}
61 |
62 |
63 |
64 | Change an Address
65 |
66 |
67 |
68 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | {% endif %}
84 |
85 |
86 | {% endblock %}
--------------------------------------------------------------------------------
/src/orders/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 |
3 | from carts.mixins import TokenMixin
4 |
5 | from .models import UserAddress, Order
6 |
7 | # parse order token
8 | # check order not complete
9 | # nonce is coming through
10 | # mark cart complete
11 | # mark order done
12 |
13 |
14 |
15 |
16 | class FinalizedOrderSerializer(TokenMixin, serializers.Serializer):
17 | order_token = serializers.CharField()
18 | payment_method_nonce = serializers.CharField()
19 | order_id = serializers.IntegerField(required=False)
20 | user_checkout_id = serializers.IntegerField(required=False)
21 |
22 |
23 | def validate(self, data):
24 | order_token = data.get("order_token")
25 | order_data = self.parse_token(order_token)
26 | order_id = order_data.get("order_id")
27 | user_checkout_id = order_data.get("user_checkout_id")
28 |
29 | try:
30 | order_obj = Order.objects.get(id=order_id, user__id=user_checkout_id)
31 | data["order_id"] = order_id
32 | data["user_checkout_id"] = user_checkout_id
33 | except:
34 | raise serializers.ValidationError("This is not a valid order for this user.")
35 |
36 | payment_method_nonce = data.get("payment_method_nonce")
37 | if payment_method_nonce == None:
38 | raise serializers.ValidationError("This is not a valid payment method nonce")
39 |
40 | return data
41 |
42 |
43 |
44 | class OrderDetailSerializer(serializers.ModelSerializer):
45 | url = serializers.HyperlinkedIdentityField(view_name="order_detail_api")
46 | subtotal = serializers.SerializerMethodField()
47 | class Meta:
48 | model = Order
49 | fields = [
50 | "url",
51 | "order_id",
52 | "user",
53 | "shipping_address",
54 | "billing_address",
55 | "shipping_total_price",
56 | "subtotal",
57 | "order_total",
58 | ]
59 |
60 | def get_subtotal(self, obj):
61 | return obj.cart.subtotal
62 |
63 |
64 |
65 | class OrderSerializer(serializers.ModelSerializer):
66 | subtotal = serializers.SerializerMethodField()
67 | class Meta:
68 | model = Order
69 | fields = [
70 | "id",
71 | "user",
72 | "shipping_address",
73 | "billing_address",
74 | "shipping_total_price",
75 | "subtotal",
76 | "order_total",
77 | ]
78 |
79 | def get_subtotal(self, obj):
80 | return obj.cart.subtotal
81 |
82 |
83 |
84 | class UserAddressSerializer(serializers.ModelSerializer):
85 | class Meta:
86 | model = UserAddress
87 | fields = [
88 | "id",
89 | "user",
90 | "type",
91 | "street",
92 | "city",
93 | "zipcode",
94 | ]
95 |
--------------------------------------------------------------------------------
/src/newsletter/views.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.core.mail import send_mail
3 | from django.shortcuts import render
4 |
5 | from products.models import ProductFeatured, Product
6 | from .forms import ContactForm, SignUpForm
7 | from .models import SignUp
8 |
9 | # Create your views here.
10 | def home(request):
11 | title = 'Sign Up Now'
12 |
13 | featured_image = ProductFeatured.objects.filter(active=True).order_by("?").first()
14 | products = Product.objects.all().order_by("?")[:6]
15 | products2 = Product.objects.all().order_by("?")[:6]
16 |
17 | form = SignUpForm(request.POST or None)
18 | context = {
19 | "title": title,
20 | "form": form,
21 | "featured_image":featured_image,
22 | "products":products,
23 | "products2":products2
24 | }
25 |
26 |
27 | if form.is_valid():
28 | #form.save()
29 | #print request.POST['email'] #not recommended
30 | instance = form.save(commit=False)
31 |
32 | full_name = form.cleaned_data.get("full_name")
33 | if not full_name:
34 | full_name = "New full name"
35 | instance.full_name = full_name
36 | # if not instance.full_name:
37 | # instance.full_name = "Justin"
38 | instance.save()
39 | context = {
40 | "title": "Thank you"
41 | }
42 |
43 | return render(request, "home.html", context)
44 |
45 |
46 |
47 | def contact(request):
48 | title = 'Contact Us'
49 | title_align_center = True
50 | form = ContactForm(request.POST or None)
51 | if form.is_valid():
52 | # for key, value in form.cleaned_data.iteritems():
53 | # print key, value
54 | # #print form.cleaned_data.get(key)
55 | form_email = form.cleaned_data.get("email")
56 | form_message = form.cleaned_data.get("message")
57 | form_full_name = form.cleaned_data.get("full_name")
58 | # print email, message, full_name
59 | subject = 'Site contact form'
60 | from_email = settings.EMAIL_HOST_USER
61 | to_email = [from_email, 'youotheremail@email.com']
62 | contact_message = "%s: %s via %s"%(
63 | form_full_name,
64 | form_message,
65 | form_email)
66 | some_html_message = """
67 |
hello
68 | """
69 | send_mail(subject,
70 | contact_message,
71 | from_email,
72 | to_email,
73 | html_message=some_html_message,
74 | fail_silently=True)
75 |
76 | context = {
77 | "form": form,
78 | "title": title,
79 | "title_align_center": title_align_center,
80 | }
81 | return render(request, "forms.html", context)
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/api_tests/api_tests.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import json
3 |
4 | cart_url = 'http://127.0.0.1:8000/api/cart/'
5 |
6 | def create_cart():
7 | # create cart
8 | cart_r = requests.get(cart_url)
9 | # get cart token
10 | cart_token = cart_r.json()["token"]
11 | return cart_token
12 |
13 |
14 | def do_api_test(email=None, user_auth=None):
15 | cart_token = create_cart()
16 | # add items to cart
17 | new_cart_url = cart_url + "?token=" + cart_token + "&item=10&qty=3"
18 | new_cart_r = requests.get(new_cart_url)
19 | #print new_cart_r.text
20 | #get user_checkout token
21 | user_checkout_url = 'http://127.0.0.1:8000/api/user/checkout/'
22 | if email:
23 | data = {
24 | "email": email
25 | }
26 | u_c_r = requests.post(user_checkout_url, data=data)
27 |
28 | user_checkout_token = u_c_r.json().get("user_checkout_token")
29 | #print user_checkout_token
30 | addresses = "http://127.0.0.1:8000/api/user/address/?checkout_token=" + user_checkout_token
31 | #address = "http://127.0.0.1:8000/api/user/address/?checkout_token=eydicmFpbnRyZWVfaWQnOiB1JzY0ODMxMzkzJywgJ3VzZXJfY2hlY2tvdXRfaWQnOiAxMSwgJ3N1Y2Nlc3MnOiBUcnVlfQ=="
32 | addresses_r = requests.get(addresses)
33 | addresses_r_data = addresses_r.json()
34 | if addresses_r_data["count"] >= 2:
35 | b_id = addresses_r_data["results"][0]["id"]
36 | s_id = addresses_r_data["results"][1]["id"]
37 | else:
38 | addresses_create = "http://127.0.0.1:8000/api/user/address/create/"
39 | user_id = 11
40 | data = {
41 | "user": user_id,
42 | "type": "billing",
43 | "street": "12423 Test",
44 | "city": "Newport Beach",
45 | "zipcode": 92304,
46 | }
47 | addresses_create_r = requests.post(addresses_create, data=data)
48 | b_id = addresses_create_r.json().get("id")
49 | data = {
50 | "user": user_id,
51 | "type": "shipping",
52 | "street": "12423 Test",
53 | "city": "Newport Beach",
54 | "zipcode": 92304,
55 | }
56 | addresses_create_s_r = requests.post(addresses_create, data=data)
57 | s_id = addresses_create_s_r.json().get("id")
58 | """
59 | do checkout
60 | """
61 | checkout_url = "http://127.0.0.1:8000/api/checkout/"
62 | data = {
63 | "billing_address": b_id,
64 | "shipping_address": s_id,
65 | "cart_token": cart_token,
66 | "checkout_token": user_checkout_token
67 | }
68 | #print data
69 | order = requests.post(checkout_url, data=data)
70 | #print order.headers
71 | print order.text
72 |
73 |
74 |
75 | do_api_test(email='abc1234@gmail.com')
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/src/products/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 |
3 |
4 | from .models import Category, Product, Variation
5 |
6 |
7 | class VariationSerializer(serializers.ModelSerializer):
8 | class Meta:
9 | model = Variation
10 | fields = [
11 | "id",
12 | "title",
13 | "price",
14 | ]
15 |
16 |
17 |
18 | class ProductDetailUpdateSerializer(serializers.ModelSerializer):
19 | variation_set = VariationSerializer(many=True, read_only=True)
20 | image = serializers.SerializerMethodField()
21 | class Meta:
22 | model = Product
23 | fields = [
24 | "id",
25 | "title",
26 | "description",
27 | "price",
28 | "image",
29 | "variation_set",
30 | ]
31 |
32 | def get_image(self, obj):
33 | try:
34 | return obj.productimage_set.first().image.url
35 | except:
36 | return None
37 |
38 | def create(self, validated_data):
39 | title = validated_data["title"]
40 | Product.objects.get(title=title)
41 | product = Product.objects.create(**validated_data)
42 | return product
43 |
44 | def update(self, instance, validated_data):
45 | instance.title = validated_data["title"]
46 | instance.save()
47 | return instance
48 | # def update
49 |
50 |
51 | class ProductDetailSerializer(serializers.ModelSerializer):
52 | variation_set = VariationSerializer(many=True, read_only=True)
53 | image = serializers.SerializerMethodField()
54 | class Meta:
55 | model = Product
56 | fields = [
57 | "id",
58 | "title",
59 | "description",
60 | "price",
61 | "image",
62 | "variation_set",
63 | ]
64 |
65 | def get_image(self, obj):
66 | return obj.productimage_set.first().image.url
67 |
68 |
69 |
70 |
71 | class ProductSerializer(serializers.ModelSerializer):
72 | url = serializers.HyperlinkedIdentityField(view_name='products_detail_api')
73 | variation_set = VariationSerializer(many=True)
74 | image = serializers.SerializerMethodField()
75 | class Meta:
76 | model = Product
77 | fields = [
78 | "url",
79 | "id",
80 | "title",
81 | "image",
82 | "variation_set",
83 | ]
84 |
85 | def get_image(self, obj):
86 | try:
87 | return obj.productimage_set.first().image.url
88 | except:
89 | return None
90 |
91 |
92 |
93 | class CategorySerializer(serializers.ModelSerializer):
94 | url = serializers.HyperlinkedIdentityField(view_name='category_detail_api')
95 | product_set = ProductSerializer(many=True)
96 | class Meta:
97 | model = Category
98 | fields = [
99 | "url",
100 | "id",
101 | "title",
102 | "description",
103 | "product_set", ## obj.product_set.all()
104 | #"default_category",
105 |
106 | ]
107 |
108 |
109 |
110 | #CREATE RETRIEVE UPDATE DESTROY
--------------------------------------------------------------------------------
/static_in_env/static_root/admin/js/timeparse.js:
--------------------------------------------------------------------------------
1 | var timeParsePatterns = [
2 | // 9
3 | { re: /^\d{1,2}$/i,
4 | handler: function(bits) {
5 | if (bits[0].length == 1) {
6 | return '0' + bits[0] + ':00';
7 | } else {
8 | return bits[0] + ':00';
9 | }
10 | }
11 | },
12 | // 13:00
13 | { re: /^\d{2}[:.]\d{2}$/i,
14 | handler: function(bits) {
15 | return bits[0].replace('.', ':');
16 | }
17 | },
18 | // 9:00
19 | { re: /^\d[:.]\d{2}$/i,
20 | handler: function(bits) {
21 | return '0' + bits[0].replace('.', ':');
22 | }
23 | },
24 | // 3 am / 3 a.m. / 3am
25 | { re: /^(\d+)\s*([ap])(?:.?m.?)?$/i,
26 | handler: function(bits) {
27 | var hour = parseInt(bits[1]);
28 | if (hour == 12) {
29 | hour = 0;
30 | }
31 | if (bits[2].toLowerCase() == 'p') {
32 | if (hour == 12) {
33 | hour = 0;
34 | }
35 | return (hour + 12) + ':00';
36 | } else {
37 | if (hour < 10) {
38 | return '0' + hour + ':00';
39 | } else {
40 | return hour + ':00';
41 | }
42 | }
43 | }
44 | },
45 | // 3.30 am / 3:15 a.m. / 3.00am
46 | { re: /^(\d+)[.:](\d{2})\s*([ap]).?m.?$/i,
47 | handler: function(bits) {
48 | var hour = parseInt(bits[1]);
49 | var mins = parseInt(bits[2]);
50 | if (mins < 10) {
51 | mins = '0' + mins;
52 | }
53 | if (hour == 12) {
54 | hour = 0;
55 | }
56 | if (bits[3].toLowerCase() == 'p') {
57 | if (hour == 12) {
58 | hour = 0;
59 | }
60 | return (hour + 12) + ':' + mins;
61 | } else {
62 | if (hour < 10) {
63 | return '0' + hour + ':' + mins;
64 | } else {
65 | return hour + ':' + mins;
66 | }
67 | }
68 | }
69 | },
70 | // noon
71 | { re: /^no/i,
72 | handler: function(bits) {
73 | return '12:00';
74 | }
75 | },
76 | // midnight
77 | { re: /^mid/i,
78 | handler: function(bits) {
79 | return '00:00';
80 | }
81 | }
82 | ];
83 |
84 | function parseTimeString(s) {
85 | for (var i = 0; i < timeParsePatterns.length; i++) {
86 | var re = timeParsePatterns[i].re;
87 | var handler = timeParsePatterns[i].handler;
88 | var bits = re.exec(s);
89 | if (bits) {
90 | return handler(bits);
91 | }
92 | }
93 | return s;
94 | }
95 |
--------------------------------------------------------------------------------
/src/carts/mixins.py:
--------------------------------------------------------------------------------
1 | import ast
2 | import base64
3 |
4 | from django.shortcuts import get_object_or_404
5 |
6 | from rest_framework import status
7 | from .models import Cart, CartItem, Variation
8 |
9 |
10 |
11 | class CartUpdateAPIMixin(object):
12 | def update_cart(self, *args, **kwargs):
13 | request = self.request
14 | cart = self.cart
15 | if cart:
16 | item_id = request.GET.get("item")
17 | delete_item = request.GET.get("delete", False)
18 | flash_message = ""
19 | item_added = False
20 | if item_id:
21 | item_instance = get_object_or_404(Variation, id=item_id)
22 | qty = request.GET.get("qty", 1)
23 | try:
24 | if int(qty) < 1:
25 | delete_item = True
26 | except:
27 | raise Http404
28 | cart_item, created = CartItem.objects.get_or_create(cart=cart, item=item_instance)
29 | if created:
30 | flash_message = "Successfully added to the cart"
31 | item_added = True
32 | if delete_item:
33 | flash_message = "Item removed successfully."
34 | cart_item.delete()
35 | else:
36 | if not created:
37 | flash_message = "Quantity has been updated successfully."
38 | cart_item.quantity = qty
39 | cart_item.save()
40 |
41 |
42 |
43 | class TokenMixin(object):
44 | token = None
45 | def create_token(self, data_dict):
46 | if type(data_dict) == type(dict()):
47 | token = base64.b64encode(str(data_dict))
48 | self.token = token
49 | return token
50 | else:
51 | raise ValueError("Creating a token must be a Python dictionary.")
52 |
53 |
54 | def parse_token(self, token=None):
55 | if token is None:
56 | return {}
57 | try:
58 | token_decoded = base64.b64decode(token)
59 | token_dict = ast.literal_eval(token_decoded)
60 | return token_dict
61 | except:
62 | return {}
63 |
64 |
65 |
66 |
67 | class CartTokenMixin(TokenMixin, object):
68 | token_param = "cart_token"
69 | token = None
70 |
71 | def get_cart_from_token(self):
72 | request = self.request
73 | response_status = status.HTTP_200_OK
74 | cart_token = request.GET.get(self.token_param)
75 |
76 | message = "This requires a vaild cart & cart token."
77 |
78 | cart_token_data = self.parse_token(cart_token)
79 | cart_id = cart_token_data.get("cart_id")
80 | try:
81 | cart = Cart.objects.get(id=int(cart_id))
82 | except:
83 | cart = None
84 |
85 | if cart == None:
86 | data = {
87 | "success": False,
88 | "message": message,
89 | }
90 | response_status = status.HTTP_400_BAD_REQUEST
91 | #return Response(data, status=status.HTTP_400_BAD_REQUEST)
92 | else:
93 | self.token = cart_token
94 | data = {
95 | "cart": cart.id,
96 | "success": True,
97 |
98 | }
99 | #return Response(data)
100 |
101 | return data, cart, response_status
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/api_tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
eCommerce 2 Test
9 |
10 |
11 |
12 |
13 |
Payment test
14 |
20 |
21 |
22 |
23 |
24 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/carts/models.py:
--------------------------------------------------------------------------------
1 | from decimal import Decimal
2 | from django.conf import settings
3 | from django.core.urlresolvers import reverse
4 | from django.db import models
5 | from django.db.models.signals import pre_save, post_save, post_delete
6 |
7 |
8 | from products.models import Variation
9 | # Create your models here.
10 |
11 |
12 | class CartItem(models.Model):
13 | cart = models.ForeignKey("Cart")
14 | item = models.ForeignKey(Variation)
15 | quantity = models.PositiveIntegerField(default=1)
16 | line_item_total = models.DecimalField(max_digits=10, decimal_places=2)
17 |
18 | def __unicode__(self):
19 | return self.item.title
20 |
21 | def remove(self):
22 | return self.item.remove_from_cart()
23 |
24 |
25 | def cart_item_pre_save_receiver(sender, instance, *args, **kwargs):
26 | qty = instance.quantity
27 | if qty >= 1:
28 | price = instance.item.get_price()
29 | line_item_total = Decimal(qty) * Decimal(price)
30 | instance.line_item_total = line_item_total
31 |
32 | pre_save.connect(cart_item_pre_save_receiver, sender=CartItem)
33 |
34 |
35 |
36 | def cart_item_post_save_receiver(sender, instance, *args, **kwargs):
37 | instance.cart.update_subtotal()
38 |
39 | post_save.connect(cart_item_post_save_receiver, sender=CartItem)
40 |
41 | post_delete.connect(cart_item_post_save_receiver, sender=CartItem)
42 |
43 |
44 | class Cart(models.Model):
45 | user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True)
46 | items = models.ManyToManyField(Variation, through=CartItem)
47 | timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
48 | updated = models.DateTimeField(auto_now_add=False, auto_now=True)
49 | subtotal = models.DecimalField(max_digits=50, decimal_places=2, default=0.00)
50 | tax_percentage = models.DecimalField(max_digits=10, decimal_places=5, default=0.085)
51 | tax_total = models.DecimalField(max_digits=50, decimal_places=2, default=0.00)
52 | total = models.DecimalField(max_digits=50, decimal_places=2, default=0.00)
53 | active = models.BooleanField(default=True)
54 | # discounts
55 | # shipping
56 |
57 | def __unicode__(self):
58 | return str(self.id)
59 |
60 | def update_subtotal(self):
61 | print "updating..."
62 | subtotal = 0
63 | items = self.cartitem_set.all()
64 | for item in items:
65 | subtotal += item.line_item_total
66 | self.subtotal = "%.2f" %(subtotal)
67 | self.save()
68 |
69 | def is_complete(self):
70 | self.active = False
71 | self.save()
72 |
73 |
74 |
75 |
76 | def do_tax_and_total_receiver(sender, instance, *args, **kwargs):
77 | subtotal = Decimal(instance.subtotal)
78 | tax_total = round(subtotal * Decimal(instance.tax_percentage), 2) #8.5%
79 | print instance.tax_percentage
80 | total = round(subtotal + Decimal(tax_total), 2)
81 | instance.tax_total = "%.2f" %(tax_total)
82 | instance.total = "%.2f" %(total)
83 | #instance.save()
84 |
85 |
86 |
87 | pre_save.connect(do_tax_and_total_receiver, sender=Cart)
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/static_in_env/static_root/admin/js/actions.min.js:
--------------------------------------------------------------------------------
1 | (function(a){var f;a.fn.actions=function(q){var b=a.extend({},a.fn.actions.defaults,q),g=a(this),e=!1,m=function(c){c?k():l();a(g).prop("checked",c).parent().parent().toggleClass(b.selectedClass,c)},h=function(){var c=a(g).filter(":checked").length;a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:_actions_icnt},!0));a(b.allToggle).prop("checked",function(){var a;c==g.length?(a=!0,k()):(a=!1,n());return a})},k=function(){a(b.acrossClears).hide();
2 | a(b.acrossQuestions).show();a(b.allContainer).hide()},p=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()},l=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()},n=function(){l();a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)};a(b.counterContainer).show();a(this).filter(":checked").each(function(c){a(this).parent().parent().toggleClass(b.selectedClass);
3 | h();1==a(b.acrossInput).val()&&p()});a(b.allToggle).show().click(function(){m(a(this).prop("checked"));h()});a("a",b.acrossQuestions).click(function(c){c.preventDefault();a(b.acrossInput).val(1);p()});a("a",b.acrossClears).click(function(c){c.preventDefault();a(b.allToggle).prop("checked",!1);n();m(0);h()});f=null;a(g).click(function(c){c||(c=window.event);var d=c.target?c.target:c.srcElement;if(f&&a.data(f)!=a.data(d)&&!0===c.shiftKey){var e=!1;a(f).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,
4 | 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(){e=!0});a('form#changelist-form button[name="index"]').click(function(a){if(e)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 e?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"}})(django.jQuery);
7 |
--------------------------------------------------------------------------------
/api_guide.md:
--------------------------------------------------------------------------------
1 | ## eCommerce 2 API Guide
2 |
3 |
4 | ### Create a RESTful API
5 | A REST API is needed to Connect a Django project to other clients. We will be using the our pre-exsisting Django Project [eCommerce 2](https://github.com/codingforentrepreneurs/ecommerce-2) to build our REST API off of.
6 |
7 | Examples of other clients that might need your API:
8 |
9 | - Native Mobile Applications:
10 | - iOS
11 | - Andriod
12 | - Windows Mobile
13 | - Phonegap (with a mix of a javascript framework)
14 |
15 | - Desktop platforms:
16 | - Mac OS X
17 | - Windows
18 | - Linux
19 |
20 | - Front-end (client side) Frameworks:
21 | - Angular.js
22 | - Ember.js
23 | - Backbone.js
24 |
25 | - Whatever comes next
26 |
27 | **Package Docs**: [Django Rest Framework](http://www.django-rest-framework.org/)
28 | **Quick Installation**:
29 |
30 | - `pip install djangorestframework`
31 |
32 | - Add `'rest_framework'` to `INSTALLED_APPS` in your Django Settings.
33 |
34 | - Run `django-admin migrate`
35 |
36 | [Installation Docs](http://www.django-rest-framework.org/#installation)
37 |
38 | ***
39 |
40 | ### Use JSON Web Tokens
41 |
42 | JSON Web Tokens allow for Authentication in API-based requests. It allows authentication to be simple, secure, and in compliance.
43 |
44 | **Installation**: `pip install djangorestframework-jwt`
45 |
46 | **Package Docs**: [Django REST framework JWT](http://getblimp.github.io/django-rest-framework-jwt/)
47 |
48 | ***
49 |
50 |
51 | ### Implement CORS for other Clients
52 |
53 | Enabling CORS will allow your web application to connect to an external client or website. Learn more about [CORS on Wikipedia](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing).
54 |
55 | **Package Docs**: [Django CORS headers](https://github.com/ottoyiu/django-cors-headers)
56 | **Quick Installation**:
57 |
58 | - `pip install django-cors-headers`
59 |
60 | - Add `'corsheaders'` to `INSTALLED_APPS` in your Django settings
61 |
62 | - Add/Update the following to your Django Settings:
63 |
64 | ```
65 | MIDDLEWARE_CLASSES = (
66 | ...
67 | 'corsheaders.middleware.CorsMiddleware',
68 | 'django.middleware.common.CommonMiddleware',
69 | ...
70 | )
71 | ```
72 | > The `...` above represents other possible pre-existing MIDDLEWARE_CLASSES. You just need to ensure `'corsheaders.middleware.CorsMiddleware',` is above `'django.middleware.common.CommonMiddleware',`
73 |
74 | ```
75 | CORS_ORIGIN_WHITELIST = (
76 | #'*', # for all domains; good for local testing.
77 | 'yourdomain.com' for your domain
78 | )
79 | ```
80 | > `CORS_ORIGIN_WHITELIST` is a list of origin hostnames that are authorized to make a cross-site HTTP request.
81 |
82 | Also consider adding:
83 | ```
84 | CORS_URLS_REGEX = r'^/api/.*$'
85 | ```
86 | > The `CORS_URLS_REGEX` configuration setting is a regular expression that will enable the URL pattern that matches the one you set in combination to your `CORS_ORIGIN_WHITELIST` so, in this case, we have enable only `yourdomain.com/api/` for cross-origin requests.
87 |
88 | Read more of many [configuration options here](https://github.com/ottoyiu/django-cors-headers#configuration) including `CORS_ALLOW_METHODS` for overriding the default HTTP methods allowed.
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/src/carts/templates/carts/view.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 |
4 |
69 |
70 | {% block content %}
71 |
72 |
73 |
74 |
75 | {% if object.cartitem_set.count < 1 %}
76 |
77 | {% include "carts/empty_cart.html" %}
78 | {% else %}
79 |
80 |
81 |
82 |
Your cart
83 |
84 |
85 |
86 | {% for item in object.cartitem_set.all %}
87 |
88 |
89 |
90 | {{ item.item.get_title }}
91 |
92 |
93 |
94 | {{ item.line_item_total }}
95 | X
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | {% endfor %}
105 |
106 |
107 |
108 | Subtotal: {{ object.subtotal }}
109 |
110 |
111 |
112 | Tax (Estimated): {{ object.tax_total }}
113 |
114 |
115 |
116 | Total: {{ object.total }}
117 |
118 |
119 |
120 |
121 | Checkout
122 |
123 |
124 |
125 |
126 |
127 |
128 | {% endif %}
129 |
130 |
131 | {% endblock %}
--------------------------------------------------------------------------------
/src/carts/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 |
3 |
4 | from orders.models import UserAddress, UserCheckout
5 | from products.models import Variation
6 |
7 |
8 | from .models import CartItem, Cart
9 | from .mixins import TokenMixin
10 |
11 | """
12 |
13 | {
14 | "cart_token": "12345",
15 | "billing_address": 1,
16 | "shipping_address": 1,
17 | "checkout_token": "12345",
18 | }
19 |
20 | """
21 | class CheckoutSerializer(TokenMixin, serializers.Serializer):
22 | checkout_token = serializers.CharField()
23 | billing_address = serializers.IntegerField()
24 | shipping_address = serializers.IntegerField()
25 | cart_token = serializers.CharField()
26 | user_checkout_id =serializers.IntegerField(required=False)
27 | cart_id = serializers.IntegerField(required=False)
28 |
29 | def validate(self, data):
30 | checkout_token = data.get("checkout_token")
31 | billing_address = data.get("billing_address")
32 | shipping_address = data.get("shipping_address")
33 | cart_token = data.get("cart_token")
34 |
35 | cart_token_data = self.parse_token(cart_token)
36 | cart_id = cart_token_data.get("cart_id")
37 | #print cart_token_data
38 |
39 |
40 | checkout_data = self.parse_token(checkout_token)
41 | user_checkout_id = checkout_data.get("user_checkout_id")
42 | #print checkout_data
43 |
44 |
45 | try:
46 | cart_obj = Cart.objects.get(id=int(cart_id))
47 | data["cart_id"] = cart_obj.id
48 | except:
49 | raise serializers.ValidationError("This is not a valid cart")
50 |
51 | try:
52 | user_checkout = UserCheckout.objects.get(id=int(user_checkout_id))
53 | data["user_checkout_id"] = user_checkout.id
54 | except:
55 | raise serializers.ValidationError("This is not a valid user")
56 |
57 |
58 | try:
59 | billing_obj = UserAddress.objects.get(user__id=int(user_checkout_id), id=int(billing_address))
60 | except:
61 | raise serializers.ValidationError("This is not a valid address for this user")
62 |
63 | try:
64 | shipping_obj = UserAddress.objects.get(user__id=int(user_checkout_id), id=int(shipping_address))
65 | except:
66 | raise serializers.ValidationError("This is not a valid address for this user")
67 |
68 | return data
69 |
70 | # def validate_
(self, value):
71 | # return value
72 | # def validate_checkout_token(self, value):
73 | # print type(value)
74 | # if type(value) == type(str()):
75 | # return value
76 | # raise serializers.ValidationError("This is not a valid token.")
77 |
78 |
79 |
80 | class CartVariationSerializer(serializers.ModelSerializer):
81 | product = serializers.SerializerMethodField()
82 | class Meta:
83 | model = Variation
84 | fields = [
85 | "id",
86 | "title",
87 | "price",
88 | "product",
89 | ]
90 |
91 | def get_product(self, obj):
92 | return obj.product.title
93 |
94 |
95 |
96 | class CartItemSerializer(serializers.ModelSerializer):
97 | #item = CartVariationSerializer(read_only=True)
98 | item = serializers.SerializerMethodField()
99 | item_title = serializers.SerializerMethodField()
100 | product = serializers.SerializerMethodField()
101 | price = serializers.SerializerMethodField()
102 | class Meta:
103 | model = CartItem
104 | fields = [
105 | "item",
106 | "item_title",
107 | "price",
108 | "product",
109 | "quantity",
110 | "line_item_total",
111 | ]
112 |
113 | def get_item(self,obj):
114 | return obj.item.id
115 |
116 | def get_item_title(self, obj):
117 | return "%s %s" %(obj.item.product.title, obj.item.title)
118 |
119 | def get_product(self, obj):
120 | return obj.item.product.id
121 |
122 | def get_price(self, obj):
123 | return obj.item.price
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/src/templates/navbar.html:
--------------------------------------------------------------------------------
1 | {% load staticfiles %}
2 |
3 |
4 |
5 |
6 |
15 |
16 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
67 |
68 | {% endif %}
69 |
70 | {% endif %}
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/src/ecommerce2/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.conf.urls import include, url
3 | from django.conf.urls.static import static
4 | from django.contrib import admin
5 |
6 | from carts.views import (
7 | CartAPIView,
8 | CartView,
9 | CheckoutAPIView,
10 | CheckoutFinalizeAPIView,
11 | CheckoutView,
12 | CheckoutFinalView,
13 | ItemCountView,
14 | )
15 | from orders.views import (
16 | AddressSelectFormView,
17 | UserAddressCreateView,
18 | UserAddressCreateAPIView,
19 | UserAddressListAPIView,
20 | UserCheckoutAPI,
21 | OrderList,
22 | OrderListAPIView,
23 | OrderDetail,
24 | OrderRetrieveAPIView,
25 | )
26 |
27 | from products.views import (
28 | APIHomeView,
29 | CategoryListAPIView,
30 | CategoryRetrieveAPIView,
31 | ProductListAPIView,
32 | ProductRetrieveAPIView,
33 |
34 | )
35 |
36 | urlpatterns = [
37 | # Examples:
38 | url(r'^$', 'newsletter.views.home', name='home'),
39 | url(r'^contact/$', 'newsletter.views.contact', name='contact'),
40 | url(r'^about/$', 'ecommerce2.views.about', name='about'),
41 | # url(r'^blog/', include('blog.urls')),
42 |
43 | url(r'^admin/', include(admin.site.urls)),
44 | url(r'^accounts/', include('registration.backends.default.urls')),
45 | url(r'^products/', include('products.urls')),
46 | url(r'^categories/', include('products.urls_categories')),
47 | url(r'^orders/$', OrderList.as_view(), name='orders'),
48 | url(r'^orders/(?P\d+)/$', OrderDetail.as_view(), name='order_detail'),
49 | url(r'^cart/$', CartView.as_view(), name='cart'),
50 | url(r'^cart/count/$', ItemCountView.as_view(), name='item_count'),
51 | url(r'^checkout/$', CheckoutView.as_view(), name='checkout'),
52 | url(r'^checkout/address/$', AddressSelectFormView.as_view(), name='order_address'),
53 | url(r'^checkout/address/add/$', UserAddressCreateView.as_view(), name='user_address_create'),
54 | url(r'^checkout/final/$', CheckoutFinalView.as_view(), name='checkout_final'),
55 |
56 | ]
57 |
58 |
59 | #API Patterns
60 | urlpatterns += [
61 | url(r'^api/$', APIHomeView.as_view(), name='home_api'),
62 |
63 | url(r'^api/cart/$', CartAPIView.as_view(), name='cart_api'),
64 | url(r'^api/checkout/$', CheckoutAPIView.as_view(), name='checkout_api'),
65 | url(r'^api/checkout/finalize/$', CheckoutFinalizeAPIView.as_view(), name='checkout_finalize_api'),
66 | url(r'^api/auth/token/$', 'rest_framework_jwt.views.obtain_jwt_token', name='auth_login_api'),
67 | url(r'^api/auth/token/refresh/$', 'rest_framework_jwt.views.refresh_jwt_token', name='refresh_token_api'),
68 | url(r'^api/user/address/$', UserAddressListAPIView.as_view(), name='user_address_list_api'),
69 | url(r'^api/user/address/create/$', UserAddressCreateAPIView.as_view(), name='user_address_create_api'),
70 | url(r'^api/user/checkout/$', UserCheckoutAPI.as_view(), name='user_checkout_api'),
71 | url(r'^api/categories/$', CategoryListAPIView.as_view(), name='categories_api'),
72 | url(r'^api/categories/(?P\d+)/$', CategoryRetrieveAPIView.as_view(), name='category_detail_api'),
73 | url(r'^api/orders/$', OrderListAPIView.as_view(), name='orders_api'),
74 | url(r'^api/orders/(?P\d+)/$', OrderRetrieveAPIView.as_view(), name='order_detail_api'),
75 | url(r'^api/products/$', ProductListAPIView.as_view(), name='products_api'),
76 | url(r'^api/products/(?P\d+)/$', ProductRetrieveAPIView.as_view(), name='products_detail_api'),
77 | ]
78 |
79 |
80 |
81 | if settings.DEBUG:
82 | urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
83 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
--------------------------------------------------------------------------------
/src/products/templates/products/product_detail.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 |
4 |
58 |
59 |
60 | {% block content %}
61 |
62 |
63 |
64 |
65 |
66 |
{{ object.title }}
67 |
68 | {% if object.get_image_url %}
69 |
70 |
71 |
72 | {% endif %}
73 |
74 |
75 |
76 |
77 | {{ object.description }}
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
117 |
118 |
119 |
120 |
121 | Share
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
Related Products
131 |
132 |
133 | {% for product in related %}
134 |
135 |
136 | {% include "products/product_thumbnail.html" with product=product price="True" %}
137 |
138 | {% cycle '' '
' %}
139 | {% endfor %}
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 | {% endblock %}
--------------------------------------------------------------------------------
/src/ecommerce2/api_tests.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import json
3 |
4 | cart_url = 'http://127.0.0.1:8000/api/cart/'
5 |
6 | def create_cart():
7 | # create cart
8 | cart_r = requests.get(cart_url)
9 | # get cart token
10 | cart_token = cart_r.json()["token"]
11 | return cart_token
12 |
13 |
14 | def do_api_test(email=None, user_auth=None):
15 | cart_token = create_cart()
16 | # add items to cart
17 | new_cart_url = cart_url + "?token=" + cart_token + "&item=10&qty=3"
18 | new_cart_r = requests.get(new_cart_url)
19 | #print new_cart_r.text
20 | #get user_checkout token
21 | user_checkout_url = 'http://127.0.0.1:8000/api/user/checkout/'
22 | if email:
23 | data = {
24 | "email": email
25 | }
26 | u_c_r = requests.post(user_checkout_url, data=data)
27 |
28 | user_checkout_token = u_c_r.json().get("user_checkout_token")
29 | #print user_checkout_token
30 | addresses = "http://127.0.0.1:8000/api/user/address/?checkout_token=" + user_checkout_token
31 | #address = "http://127.0.0.1:8000/api/user/address/?checkout_token=eydicmFpbnRyZWVfaWQnOiB1JzY0ODMxMzkzJywgJ3VzZXJfY2hlY2tvdXRfaWQnOiAxMSwgJ3N1Y2Nlc3MnOiBUcnVlfQ=="
32 | addresses_r = requests.get(addresses)
33 | addresses_r_data = addresses_r.json()
34 | if addresses_r_data["count"] >= 2:
35 | b_id = addresses_r_data["results"][0]["id"]
36 | s_id = addresses_r_data["results"][1]["id"]
37 | else:
38 | addresses_create = "http://127.0.0.1:8000/api/user/address/create/"
39 | user_id = 11
40 | data = {
41 | "user": user_id,
42 | "type": "billing",
43 | "street": "12423 Test",
44 | "city": "Newport Beach",
45 | "zipcode": 92304,
46 | }
47 | addresses_create_r = requests.post(addresses_create, data=data)
48 | b_id = addresses_create_r.json().get("id")
49 | data = {
50 | "user": user_id,
51 | "type": "shipping",
52 | "street": "12423 Test",
53 | "city": "Newport Beach",
54 | "zipcode": 92304,
55 | }
56 | addresses_create_s_r = requests.post(addresses_create, data=data)
57 | s_id = addresses_create_s_r.json().get("id")
58 | """
59 | do checkout
60 | """
61 | checkout_url = "http://127.0.0.1:8000/api/checkout/"
62 | data = {
63 | "billing_address": b_id,
64 | "shipping_address": s_id,
65 | "cart_token": cart_token,
66 | "checkout_token": user_checkout_token
67 | }
68 | #print data
69 | order = requests.post(checkout_url, data=data)
70 | #print order.headers
71 | print order.text
72 |
73 |
74 |
75 | do_api_test(email='abc1234@gmail.com')
76 |
77 |
78 |
79 |
80 |
81 |
82 | # base_url = "http://127.0.0.1:8000/api/"
83 |
84 | # login_url = base_url + "auth/token/"
85 |
86 | # products_url = base_url + "products/"
87 |
88 | # refresh_url = login_url + "refresh/"
89 |
90 |
91 | # cart_url = base_url + "cart/"
92 | # #requests.get
93 | # #requests.post(login_url, data=None, headers=None, params=None)
94 |
95 |
96 | # data = {
97 | # "username": "jmitchel3",
98 | # "password": "123"
99 | # }
100 | # login_r = requests.post(login_url, data=data)
101 | # login_r.text
102 | # json_data = login_r.json()
103 |
104 | # import json
105 |
106 | # print(json.dumps(json_data, indent=2))
107 |
108 | # token = json_data["token"]
109 | # print token
110 |
111 | # headers = {
112 | # "Content-Type": "application/json",
113 | # "Authorization": "JWT %s" %(token),
114 | # }
115 |
116 | # p_r = requests.get(products_url, headers=headers)
117 | # print p_r.text
118 | # print(json.dumps(p_r.json(), indent=2))
119 |
120 |
121 |
122 | # #Refresh URL TOKEN
123 |
124 | # data = {
125 | # "token": token
126 | # }
127 | # refresh_r = requests.post(refresh_url, data=data)
128 |
129 |
130 | # print refresh_r.json()
131 |
132 | # token = refresh_r.json()["token"]
133 |
134 |
135 |
136 | # cart_r = requests.get(cart_url)
137 | # cart_token = cart_r.json()["token"]
138 |
139 |
140 | # new_cart_url = cart_url + "?token=" + cart_token + "&item=10&qty=3&delete=True"
141 |
142 | # new_cart_r = requests.get(new_cart_url)
143 | # print new_cart_r.json()
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/src/orders/models.py:
--------------------------------------------------------------------------------
1 | from decimal import Decimal
2 | from django.conf import settings
3 | from django.core.urlresolvers import reverse
4 | from django.db import models
5 | from django.db.models.signals import pre_save, post_save
6 | # Create your models here.
7 | from carts.models import Cart
8 |
9 |
10 | import braintree
11 |
12 | if settings.DEBUG:
13 | braintree.Configuration.configure(braintree.Environment.Sandbox,
14 | merchant_id=settings.BRAINTREE_MERCHANT_ID,
15 | public_key=settings.BRAINTREE_PUBLIC,
16 | private_key=settings.BRAINTREE_PRIVATE)
17 |
18 |
19 |
20 | class UserCheckout(models.Model):
21 | user = models.OneToOneField(settings.AUTH_USER_MODEL, null=True, blank=True) #not required
22 | email = models.EmailField(unique=True) #--> required
23 | braintree_id = models.CharField(max_length=120, null=True, blank=True)
24 |
25 | def __unicode__(self): #def __str__(self):
26 | return self.email
27 |
28 | @property
29 | def get_braintree_id(self,):
30 | instance = self
31 | if not instance.braintree_id:
32 | result = braintree.Customer.create({
33 | "email": instance.email,
34 | })
35 | if result.is_success:
36 | instance.braintree_id = result.customer.id
37 | instance.save()
38 | return instance.braintree_id
39 |
40 | def get_client_token(self):
41 | customer_id = self.get_braintree_id
42 | if customer_id:
43 | client_token = braintree.ClientToken.generate({
44 | "customer_id": customer_id
45 | })
46 | return client_token
47 | return None
48 |
49 |
50 | def update_braintree_id(sender, instance, *args, **kwargs):
51 | if not instance.braintree_id:
52 | instance.get_braintree_id
53 |
54 |
55 | post_save.connect(update_braintree_id, sender=UserCheckout)
56 |
57 |
58 |
59 |
60 | ADDRESS_TYPE = (
61 | ('billing', 'Billing'),
62 | ('shipping', 'Shipping'),
63 | )
64 |
65 | class UserAddress(models.Model):
66 | user = models.ForeignKey(UserCheckout)
67 | type = models.CharField(max_length=120, choices=ADDRESS_TYPE)
68 | street = models.CharField(max_length=120)
69 | city = models.CharField(max_length=120)
70 | state = models.CharField(max_length=120)
71 | zipcode = models.CharField(max_length=120)
72 |
73 | def __unicode__(self):
74 | return self.street
75 |
76 | def get_address(self):
77 | return "%s, %s, %s %s" %(self.street, self.city, self.state, self.zipcode)
78 |
79 |
80 | ORDER_STATUS_CHOICES = (
81 | ('created', 'Created'),
82 | ('paid', 'Paid'),
83 | ('shipped', 'Shipped'),
84 | ('refunded', 'Refunded'),
85 | )
86 |
87 |
88 | class Order(models.Model):
89 | status = models.CharField(max_length=120, choices=ORDER_STATUS_CHOICES, default='created')
90 | cart = models.ForeignKey(Cart)
91 | user = models.ForeignKey(UserCheckout, null=True)
92 | billing_address = models.ForeignKey(UserAddress, related_name='billing_address', null=True)
93 | shipping_address = models.ForeignKey(UserAddress, related_name='shipping_address', null=True)
94 | shipping_total_price = models.DecimalField(max_digits=50, decimal_places=2, default=5.99)
95 | order_total = models.DecimalField(max_digits=50, decimal_places=2, )
96 | order_id = models.CharField(max_length=20, null=True, blank=True)
97 |
98 | def __unicode__(self):
99 | return "Order_id: %s, Cart_id: %s"%(self.id, self.cart.id)
100 |
101 | class Meta:
102 | ordering = ['-id']
103 |
104 | def get_absolute_url(self):
105 | return reverse("order_detail", kwargs={"pk": self.pk})
106 |
107 | def mark_completed(self, order_id=None):
108 | self.status = "paid"
109 | if order_id and not self.order_id:
110 | self.order_id = order_id
111 | self.save()
112 |
113 | @property
114 | def is_complete(self):
115 | if self.status == "paid":
116 | return True
117 | return False
118 |
119 |
120 | def order_pre_save(sender, instance, *args, **kwargs):
121 | shipping_total_price = instance.shipping_total_price
122 | cart_total = instance.cart.total
123 | order_total = Decimal(shipping_total_price) + Decimal(cart_total)
124 | instance.order_total = order_total
125 |
126 | pre_save.connect(order_pre_save, sender=Order)
127 |
128 | # #if status == "refunded":
129 | # braintree refud
130 | # post_save.connect()
131 |
132 | #
--------------------------------------------------------------------------------
/src/ecommerce2/old_settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for ecommerce2 project.
3 |
4 | Generated by 'django-admin startproject' using Django 1.8.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.8/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/1.8/ref/settings/
11 | """
12 |
13 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
14 | import os
15 |
16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17 | #root of project
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = 'csqwlmc8s55o($rt6ozh7u+ui9zb-et00w$d90j8$^!nvj41_r'
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = True
27 |
28 | ALLOWED_HOSTS = []
29 |
30 | EMAIL_HOST = 'smtp.gmail.com'
31 | EMAIL_HOST_USER = 'yourgmail@gmail.com'
32 | EMAIL_HOST_PASSWORD = 'yourpassword'
33 | EMAIL_PORT = 587
34 | EMAIL_USE_TLS = True
35 |
36 | '''
37 | If using gmail, you will need to
38 | unlock Captcha to enable Django
39 | to send for you:
40 | https://accounts.google.com/displayunlockcaptcha
41 | '''
42 |
43 |
44 |
45 | # Application definition
46 |
47 | INSTALLED_APPS = (
48 | #django app
49 | 'django.contrib.admin',
50 | 'django.contrib.auth',
51 | 'django.contrib.contenttypes',
52 | 'django.contrib.sessions',
53 | 'django.contrib.sites',
54 | 'django.contrib.messages',
55 | 'django.contrib.staticfiles',
56 | #third party apps
57 | 'crispy_forms',
58 | 'registration',
59 | #my apps
60 | 'newsletter',
61 | )
62 |
63 | MIDDLEWARE_CLASSES = (
64 | 'django.contrib.sessions.middleware.SessionMiddleware',
65 | 'django.middleware.common.CommonMiddleware',
66 | 'django.middleware.csrf.CsrfViewMiddleware',
67 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
68 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
69 | 'django.contrib.messages.middleware.MessageMiddleware',
70 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
71 | 'django.middleware.security.SecurityMiddleware',
72 | )
73 |
74 | ROOT_URLCONF = 'ecommerce2.urls'
75 |
76 | TEMPLATES = [
77 | {
78 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
79 | 'DIRS': [os.path.join(BASE_DIR, "templates")],
80 | 'APP_DIRS': True,
81 | 'OPTIONS': {
82 | 'context_processors': [
83 | 'django.template.context_processors.debug',
84 | 'django.template.context_processors.request',
85 | 'django.contrib.auth.context_processors.auth',
86 | 'django.contrib.messages.context_processors.messages',
87 | ],
88 | },
89 | },
90 | ]
91 |
92 | WSGI_APPLICATION = 'ecommerce2.wsgi.application'
93 |
94 |
95 | # Database
96 | # https://docs.djangoproject.com/en/1.8/ref/settings/#databases
97 |
98 | DATABASES = {
99 | 'default': {
100 | 'ENGINE': 'django.db.backends.sqlite3',
101 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
102 | }
103 | }
104 |
105 |
106 | # Internationalization
107 | # https://docs.djangoproject.com/en/1.8/topics/i18n/
108 |
109 | LANGUAGE_CODE = 'en-us'
110 |
111 | TIME_ZONE = 'UTC'
112 |
113 | USE_I18N = True
114 |
115 | USE_L10N = True
116 |
117 | USE_TZ = True
118 |
119 |
120 | # Static files (CSS, JavaScript, Images)
121 | # https://docs.djangoproject.com/en/1.8/howto/static-files/
122 |
123 | STATIC_URL = '/static/'
124 |
125 | STATIC_ROOT = os.path.join(os.path.dirname(BASE_DIR), "static_in_env", "static_root")
126 |
127 | STATICFILES_DIRS = (
128 | os.path.join(BASE_DIR, "static_in_pro", "our_static"),
129 | #os.path.join(BASE_DIR, "static_in_env"),
130 | #'/var/www/static/',
131 | )
132 |
133 | MEDIA_URL = '/media/'
134 | MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), "static_in_env", "media_root")
135 |
136 |
137 |
138 | #Crispy FORM TAGs SETTINGS
139 | CRISPY_TEMPLATE_PACK = 'bootstrap3'
140 |
141 |
142 | #DJANGO REGISTRATION REDUX SETTINGS
143 | ACCOUNT_ACTIVATION_DAYS = 7
144 | REGISTRATION_AUTO_LOGIN = True
145 | SITE_ID = 1
146 | LOGIN_REDIRECT_URL = '/'
147 |
148 |
149 |
150 |
--------------------------------------------------------------------------------
/src/templates/home.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% load crispy_forms_tags %}
3 | {% load staticfiles %}
4 |
5 |
6 | {% block head_title %}Welcome | {{ block.super }}{% endblock %}
7 |
8 |
28 |
29 |
30 |
31 | {% block jumbotron %}
32 |
33 | {% if featured_image %}
34 |
35 |
36 |
37 |
38 |
{{ featured_image.product.title }}
39 |
{{ featured_image.product.description }}
40 |
41 | More Details
42 |
43 |
44 | {% if not featured_image.make_image_background %}
45 |
46 |
47 |
48 | {% endif %}
49 |
50 |
51 |
52 |
53 |
54 | {% else %}
55 |
56 |
57 |
58 |
59 |
Try Django 1.8
60 |
The MVP Landing project is designed to get your project started. The goal is to help you launch as soon as possible with the least amount of investment using time or money. Join Us today.
61 |
62 | Join Us »
63 |
64 |
65 |
66 | VIDEO
67 |
68 |
69 |
70 |
71 |
72 |
73 | {% endif %}
74 |
75 |
76 | {% endblock %}
77 |
78 |
79 |
80 |
81 | {% block content %}
82 |
83 |
84 |
Recommended Products
85 | {% include "products/products.html" with object_list=products col_class_set="col-sm-2" %}
86 |
87 |
88 |
89 |
90 |
Featured Products
91 | {% include "products/products.html" with object_list=products2 %}
92 |
93 |
94 |
95 |
96 |
161 |
162 | {% endblock %}
--------------------------------------------------------------------------------
/src/ecommerce2/settings/base.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for ecommerce2 project.
3 |
4 | Generated by 'django-admin startproject' using Django 1.8.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.8/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/1.8/ref/settings/
11 | """
12 |
13 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
14 | import os
15 |
16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
17 | #root of project
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = 'csqwlmc8s55o($rt6ozh7u+ui9zb-et00w$d90j8$^!nvj41_r'
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = False
27 |
28 | ALLOWED_HOSTS = []
29 |
30 | EMAIL_HOST = 'smtp.gmail.com'
31 | EMAIL_HOST_USER = 'yourgmail@gmail.com'
32 | EMAIL_HOST_PASSWORD = 'yourpassword'
33 | EMAIL_PORT = 587
34 | EMAIL_USE_TLS = True
35 |
36 | '''
37 | If using gmail, you will need to
38 | unlock Captcha to enable Django
39 | to send for you:
40 | https://accounts.google.com/displayunlockcaptcha
41 | '''
42 |
43 |
44 |
45 | # Application definition
46 |
47 | INSTALLED_APPS = (
48 | #django app
49 | 'django.contrib.admin',
50 | 'django.contrib.auth',
51 | 'django.contrib.contenttypes',
52 | 'django.contrib.sessions',
53 | 'django.contrib.sites',
54 | 'django.contrib.messages',
55 | 'django.contrib.staticfiles',
56 | #third party apps
57 | 'crispy_forms',
58 | 'registration',
59 | #my apps
60 | 'newsletter',
61 | )
62 |
63 | MIDDLEWARE_CLASSES = (
64 | 'django.contrib.sessions.middleware.SessionMiddleware',
65 | 'django.middleware.common.CommonMiddleware',
66 | 'django.middleware.csrf.CsrfViewMiddleware',
67 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
68 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
69 | 'django.contrib.messages.middleware.MessageMiddleware',
70 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
71 | 'django.middleware.security.SecurityMiddleware',
72 | )
73 |
74 | ROOT_URLCONF = 'ecommerce2.urls'
75 |
76 | TEMPLATES = [
77 | {
78 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
79 | 'DIRS': [os.path.join(BASE_DIR, "templates")],
80 | 'APP_DIRS': True,
81 | 'OPTIONS': {
82 | 'context_processors': [
83 | 'django.template.context_processors.debug',
84 | 'django.template.context_processors.request',
85 | 'django.contrib.auth.context_processors.auth',
86 | 'django.contrib.messages.context_processors.messages',
87 | ],
88 | },
89 | },
90 | ]
91 |
92 | WSGI_APPLICATION = 'ecommerce2.wsgi.application'
93 |
94 |
95 | # Database
96 | # https://docs.djangoproject.com/en/1.8/ref/settings/#databases
97 |
98 | DATABASES = {
99 | 'default': {
100 | 'ENGINE': 'django.db.backends.sqlite3',
101 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
102 | }
103 | }
104 |
105 |
106 | # Internationalization
107 | # https://docs.djangoproject.com/en/1.8/topics/i18n/
108 |
109 | LANGUAGE_CODE = 'en-us'
110 |
111 | TIME_ZONE = 'UTC'
112 |
113 | USE_I18N = True
114 |
115 | USE_L10N = True
116 |
117 | USE_TZ = True
118 |
119 |
120 | # Static files (CSS, JavaScript, Images)
121 | # https://docs.djangoproject.com/en/1.8/howto/static-files/
122 |
123 | STATIC_URL = '/static/'
124 |
125 | STATIC_ROOT = os.path.join(os.path.dirname(BASE_DIR), "static_in_env", "static_root")
126 |
127 | STATICFILES_DIRS = (
128 | os.path.join(BASE_DIR, "static_in_pro", "our_static"),
129 | #os.path.join(BASE_DIR, "static_in_env"),
130 | #'/var/www/static/',
131 | )
132 |
133 | MEDIA_URL = '/media/'
134 | MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), "static_in_env", "media_root")
135 |
136 |
137 |
138 | #Crispy FORM TAGs SETTINGS
139 | CRISPY_TEMPLATE_PACK = 'bootstrap3'
140 |
141 |
142 | #DJANGO REGISTRATION REDUX SETTINGS
143 | ACCOUNT_ACTIVATION_DAYS = 7
144 | REGISTRATION_AUTO_LOGIN = True
145 | SITE_ID = 1
146 | LOGIN_REDIRECT_URL = '/'
147 |
148 |
149 |
150 |
--------------------------------------------------------------------------------
/static_in_env/static_root/admin/js/inlines.min.js:
--------------------------------------------------------------------------------
1 | (function(a){a.fn.formset=function(g){var b=a.extend({},a.fn.formset.defaults,g),i=a(this);g=i.parent();var m=function(e,k,h){var j=RegExp("("+k+"-(\\d+|__prefix__))");k=k+"-"+h;a(e).prop("for")&&a(e).prop("for",a(e).prop("for").replace(j,k));if(e.id)e.id=e.id.replace(j,k);if(e.name)e.name=e.name.replace(j,k)},l=a("#id_"+b.prefix+"-TOTAL_FORMS").prop("autocomplete","off"),d=parseInt(l.val(),10),c=a("#id_"+b.prefix+"-MAX_NUM_FORMS").prop("autocomplete","off");l=c.val()===""||c.val()-l.val()>0;i.each(function(){a(this).not("."+
2 | b.emptyCssClass).addClass(b.formCssClass)});if(i.length&&l){var f;if(i.prop("tagName")=="TR"){i=this.eq(-1).children().length;g.append(''+b.addText+" ");f=g.find("tr:last a")}else{i.filter(":last").after('");f=i.filter(":last").next().find("a")}f.click(function(e){e.preventDefault();var k=a("#id_"+b.prefix+"-TOTAL_FORMS");e=a("#"+
3 | b.prefix+"-empty");var h=e.clone(true);h.removeClass(b.emptyCssClass).addClass(b.formCssClass).attr("id",b.prefix+"-"+d);if(h.is("tr"))h.children(":last").append('");else h.is("ul")||h.is("ol")?h.append(''+b.deleteText+" "):h.children(":first").append(''+b.deleteText+" ");
4 | h.find("*").each(function(){m(this,b.prefix,k.val())});h.insertBefore(a(e));a(k).val(parseInt(k.val(),10)+1);d+=1;c.val()!==""&&c.val()-k.val()<=0&&f.parent().hide();h.find("a."+b.deleteCssClass).click(function(j){j.preventDefault();j=a(this).parents("."+b.formCssClass);j.remove();d-=1;b.removed&&b.removed(j);j=a("."+b.formCssClass);a("#id_"+b.prefix+"-TOTAL_FORMS").val(j.length);if(c.val()===""||c.val()-j.length>0)f.parent().show();for(var n=0,o=j.length;n b) return 1;
100 | if (a < b) return -1;
101 | }
102 | catch (e) {
103 | // silently fail on IE 'unknown' exception
104 | }
105 | return 0;
106 | } );
107 | },
108 | select_all: function(id) {
109 | var box = document.getElementById(id);
110 | for (var i = 0; i < box.options.length; i++) {
111 | box.options[i].selected = 'selected';
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/static_in_env/static_root/admin/js/admin/RelatedObjectLookups.js:
--------------------------------------------------------------------------------
1 | // Handles related-objects functionality: lookup link for raw_id_fields
2 | // and Add Another links.
3 |
4 | function html_unescape(text) {
5 | // Unescape a string that was escaped using django.utils.html.escape.
6 | text = text.replace(/</g, '<');
7 | text = text.replace(/>/g, '>');
8 | text = text.replace(/"/g, '"');
9 | text = text.replace(/'/g, "'");
10 | text = text.replace(/&/g, '&');
11 | return text;
12 | }
13 |
14 | // IE doesn't accept periods or dashes in the window name, but the element IDs
15 | // we use to generate popup window names may contain them, therefore we map them
16 | // to allowed characters in a reversible way so that we can locate the correct
17 | // element when the popup window is dismissed.
18 | function id_to_windowname(text) {
19 | text = text.replace(/\./g, '__dot__');
20 | text = text.replace(/\-/g, '__dash__');
21 | return text;
22 | }
23 |
24 | function windowname_to_id(text) {
25 | text = text.replace(/__dot__/g, '.');
26 | text = text.replace(/__dash__/g, '-');
27 | return text;
28 | }
29 |
30 | function showAdminPopup(triggeringLink, name_regexp) {
31 | var name = triggeringLink.id.replace(name_regexp, '');
32 | name = id_to_windowname(name);
33 | var href = triggeringLink.href;
34 | if (href.indexOf('?') == -1) {
35 | href += '?_popup=1';
36 | } else {
37 | href += '&_popup=1';
38 | }
39 | var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
40 | win.focus();
41 | return false;
42 | }
43 |
44 | function showRelatedObjectLookupPopup(triggeringLink) {
45 | return showAdminPopup(triggeringLink, /^lookup_/);
46 | }
47 |
48 | function dismissRelatedLookupPopup(win, chosenId) {
49 | var name = windowname_to_id(win.name);
50 | var elem = document.getElementById(name);
51 | if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) {
52 | elem.value += ',' + chosenId;
53 | } else {
54 | document.getElementById(name).value = chosenId;
55 | }
56 | win.close();
57 | }
58 |
59 | function showRelatedObjectPopup(triggeringLink) {
60 | var name = triggeringLink.id.replace(/^(change|add|delete)_/, '');
61 | name = id_to_windowname(name);
62 | var href = triggeringLink.href;
63 | var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
64 | win.focus();
65 | return false;
66 | }
67 |
68 | function dismissAddRelatedObjectPopup(win, newId, newRepr) {
69 | // newId and newRepr are expected to have previously been escaped by
70 | // django.utils.html.escape.
71 | newId = html_unescape(newId);
72 | newRepr = html_unescape(newRepr);
73 | var name = windowname_to_id(win.name);
74 | var elem = document.getElementById(name);
75 | var o;
76 | if (elem) {
77 | var elemName = elem.nodeName.toUpperCase();
78 | if (elemName == 'SELECT') {
79 | o = new Option(newRepr, newId);
80 | elem.options[elem.options.length] = o;
81 | o.selected = true;
82 | } else if (elemName == 'INPUT') {
83 | if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) {
84 | elem.value += ',' + newId;
85 | } else {
86 | elem.value = newId;
87 | }
88 | }
89 | // Trigger a change event to update related links if required.
90 | django.jQuery(elem).trigger('change');
91 | } else {
92 | var toId = name + "_to";
93 | o = new Option(newRepr, newId);
94 | SelectBox.add_to_cache(toId, o);
95 | SelectBox.redisplay(toId);
96 | }
97 | win.close();
98 | }
99 |
100 | function dismissChangeRelatedObjectPopup(win, objId, newRepr, newId) {
101 | objId = html_unescape(objId);
102 | newRepr = html_unescape(newRepr);
103 | var id = windowname_to_id(win.name).replace(/^edit_/, '');
104 | var selectsSelector = interpolate('#%s, #%s_from, #%s_to', [id, id, id]);
105 | var selects = django.jQuery(selectsSelector);
106 | selects.find('option').each(function() {
107 | if (this.value == objId) {
108 | this.innerHTML = newRepr;
109 | this.value = newId;
110 | }
111 | });
112 | win.close();
113 | };
114 |
115 | function dismissDeleteRelatedObjectPopup(win, objId) {
116 | objId = html_unescape(objId);
117 | var id = windowname_to_id(win.name).replace(/^delete_/, '');
118 | var selectsSelector = interpolate('#%s, #%s_from, #%s_to', [id, id, id]);
119 | var selects = django.jQuery(selectsSelector);
120 | selects.find('option').each(function() {
121 | if (this.value == objId) {
122 | django.jQuery(this).remove();
123 | }
124 | }).trigger('change');
125 | win.close();
126 | };
127 |
128 | // Kept for backward compatibility
129 | showAddAnotherPopup = showRelatedObjectPopup;
130 | dismissAddAnotherPopup = dismissAddRelatedObjectPopup;
131 |
--------------------------------------------------------------------------------
/src/ecommerce2/settings/production.py:
--------------------------------------------------------------------------------
1 |
2 | #static is here mvpland_static
3 | #postgresql -- mvpland
4 | #username -- cfedeploy
5 | #password -- ##
6 |
7 |
8 | """
9 | Django settings for ecommerce2 project.
10 |
11 | Generated by 'django-admin startproject' using Django 1.8.
12 |
13 | For more information on this file, see
14 | https://docs.djangoproject.com/en/1.8/topics/settings/
15 |
16 | For the full list of settings and their values, see
17 | https://docs.djangoproject.com/en/1.8/ref/settings/
18 | """
19 |
20 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
21 |
22 | from django.conf import settings
23 |
24 | if not settings.DEBUG:
25 | import os
26 |
27 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
28 | #root of project
29 |
30 | # Quick-start development settings - unsuitable for production
31 | # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
32 |
33 | # SECURITY WARNING: keep the secret key used in production secret!
34 | SECRET_KEY = 'csqwlmc8s55o($rt6ozh7u+ui9zb-et00w$d90j8$^!nvj41_r'
35 |
36 | # SECURITY WARNING: don't run with debug turned on in production!
37 | DEBUG = False
38 |
39 | ADMINS = (
40 | ("Justin", "codingforentrepreneurs@gmail.com"),
41 |
42 | )
43 |
44 | ALLOWED_HOSTS = ['cfedeploy.webfactional.com', 'trydjango.com', 'www.trydjango.com']
45 | #purchasing domain name http://name.com
46 |
47 | EMAIL_HOST = 'smtp.gmail.com'
48 | EMAIL_HOST_USER = 'yourgmail@gmail.com'
49 | EMAIL_HOST_PASSWORD = 'yourpassword'
50 | EMAIL_PORT = 587
51 | EMAIL_USE_TLS = True
52 |
53 | '''
54 | If using gmail, you will need to
55 | unlock Captcha to enable Django
56 | to send for you:
57 | https://accounts.google.com/displayunlockcaptcha
58 | '''
59 |
60 |
61 |
62 | # Application definition
63 |
64 | INSTALLED_APPS = (
65 | #django app
66 | 'django.contrib.admin',
67 | 'django.contrib.auth',
68 | 'django.contrib.contenttypes',
69 | 'django.contrib.sessions',
70 | 'django.contrib.sites',
71 | 'django.contrib.messages',
72 | 'django.contrib.staticfiles',
73 | #third party apps
74 | 'crispy_forms',
75 | 'registration',
76 | #my apps
77 | 'newsletter',
78 | )
79 |
80 | MIDDLEWARE_CLASSES = (
81 | 'django.contrib.sessions.middleware.SessionMiddleware',
82 | 'django.middleware.common.CommonMiddleware',
83 | 'django.middleware.csrf.CsrfViewMiddleware',
84 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
85 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
86 | 'django.contrib.messages.middleware.MessageMiddleware',
87 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
88 | 'django.middleware.security.SecurityMiddleware',
89 | )
90 |
91 | ROOT_URLCONF = 'ecommerce2.urls'
92 |
93 | TEMPLATES = [
94 | {
95 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
96 | 'DIRS': [os.path.join(BASE_DIR, "templates")],
97 | 'APP_DIRS': True,
98 | 'OPTIONS': {
99 | 'context_processors': [
100 | 'django.template.context_processors.debug',
101 | 'django.template.context_processors.request',
102 | 'django.contrib.auth.context_processors.auth',
103 | 'django.contrib.messages.context_processors.messages',
104 | ],
105 | },
106 | },
107 | ]
108 |
109 | WSGI_APPLICATION = 'ecommerce2.wsgi.application'
110 |
111 |
112 | # Database
113 | # https://docs.djangoproject.com/en/1.8/ref/settings/#databases
114 |
115 | from .db_password import DBPASS
116 |
117 | DATABASES = {
118 | 'default': {
119 | 'ENGINE': 'django.db.backends.postgresql_psycopg2',
120 | 'NAME': "mvpland",
121 | 'USER': "cfedeploy",
122 | 'PASSWORD': DBPASS,
123 | }
124 | }
125 |
126 |
127 | # Internationalization
128 | # https://docs.djangoproject.com/en/1.8/topics/i18n/
129 |
130 | LANGUAGE_CODE = 'en-us'
131 |
132 | TIME_ZONE = 'UTC'
133 |
134 | USE_I18N = True
135 |
136 | USE_L10N = True
137 |
138 | USE_TZ = True
139 |
140 |
141 | # Static files (CSS, JavaScript, Images)
142 | # https://docs.djangoproject.com/en/1.8/howto/static-files/
143 |
144 | STATIC_URL = '/static/'
145 |
146 | STATIC_ROOT = '/home/cfedeploy/webapps/mvpland_static/'
147 | #os.path.join(os.path.dirname(BASE_DIR), "static_in_env", "static_root")
148 |
149 | STATICFILES_DIRS = (
150 | os.path.join(BASE_DIR, "static_in_pro", "our_static"),
151 | #os.path.join(BASE_DIR, "static_in_env"),
152 | #'/var/www/static/',
153 | )
154 |
155 | MEDIA_URL = '/media/'
156 | MEDIA_ROOT = '/home/cfedeploy/webapps/mvpland_media/'
157 | #os.path.join(os.path.dirname(BASE_DIR), "static_in_env", "media_root")
158 |
159 |
160 | #Crispy FORM TAGs SETTINGS
161 | CRISPY_TEMPLATE_PACK = 'bootstrap3'
162 |
163 |
164 | #DJANGO REGISTRATION REDUX SETTINGS
165 | ACCOUNT_ACTIVATION_DAYS = 7
166 | REGISTRATION_AUTO_LOGIN = True
167 | SITE_ID = 1
168 | LOGIN_REDIRECT_URL = '/'
169 |
170 |
171 |
172 |
173 |
174 |
--------------------------------------------------------------------------------
/static_in_env/static_root/admin/css/rtl.css:
--------------------------------------------------------------------------------
1 | body {
2 | direction: rtl;
3 | }
4 |
5 | /* LOGIN */
6 |
7 | .login .form-row {
8 | float: right;
9 | }
10 |
11 | .login .form-row label {
12 | float: right;
13 | padding-left: 0.5em;
14 | padding-right: 0;
15 | text-align: left;
16 | }
17 |
18 | .login .submit-row {
19 | clear: both;
20 | padding: 1em 9.4em 0 0;
21 | }
22 |
23 | /* GLOBAL */
24 |
25 | th {
26 | text-align: right;
27 | }
28 |
29 | .module h2, .module caption {
30 | text-align: right;
31 | }
32 |
33 | .addlink, .changelink {
34 | padding-left: 0px;
35 | padding-right: 12px;
36 | background-position: 100% 0.2em;
37 | }
38 |
39 | .deletelink {
40 | padding-left: 0px;
41 | padding-right: 12px;
42 | background-position: 100% 0.25em;
43 | }
44 |
45 | .object-tools {
46 | float: left;
47 | }
48 |
49 | thead th:first-child,
50 | tfoot td:first-child {
51 | border-left: 1px solid #ddd !important;
52 | }
53 |
54 | /* LAYOUT */
55 |
56 | #user-tools {
57 | right: auto;
58 | left: 0;
59 | text-align: left;
60 | }
61 |
62 | div.breadcrumbs {
63 | text-align: right;
64 | }
65 |
66 | #content-main {
67 | float: right;
68 | }
69 |
70 | #content-related {
71 | float: left;
72 | margin-left: -19em;
73 | margin-right: auto;
74 | }
75 |
76 | .colMS {
77 | margin-left: 20em !important;
78 | margin-right: 10px !important;
79 | }
80 |
81 | /* SORTABLE TABLES */
82 |
83 | table thead th.sorted .sortoptions {
84 | float: left;
85 | }
86 |
87 | thead th.sorted .text {
88 | padding-right: 0;
89 | padding-left: 42px;
90 | }
91 |
92 | /* dashboard styles */
93 |
94 | .dashboard .module table td a {
95 | padding-left: .6em;
96 | padding-right: 12px;
97 | }
98 |
99 | /* changelists styles */
100 |
101 | .change-list .filtered {
102 | background: white url(../img/changelist-bg_rtl.gif) top left repeat-y !important;
103 | }
104 |
105 | .change-list .filtered table {
106 | border-left: 1px solid #ddd;
107 | border-right: 0px none;
108 | }
109 |
110 | #changelist-filter {
111 | right: auto;
112 | left: 0;
113 | border-left: 0px none;
114 | border-right: 1px solid #ddd;
115 | }
116 |
117 | .change-list .filtered .results, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull {
118 | margin-right: 0px !important;
119 | margin-left: 160px !important;
120 | }
121 |
122 | #changelist-filter li.selected {
123 | border-left: 0px none;
124 | padding-left: 0px;
125 | margin-left: 0;
126 | border-right: 5px solid #ccc;
127 | padding-right: 5px;
128 | margin-right: -10px;
129 | }
130 |
131 | .filtered .actions {
132 | border-left:1px solid #DDDDDD;
133 | margin-left:160px !important;
134 | border-right: 0 none;
135 | margin-right:0 !important;
136 | }
137 |
138 | #changelist table tbody td:first-child, #changelist table tbody th:first-child {
139 | border-right: 0;
140 | border-left: 1px solid #ddd;
141 | }
142 |
143 | /* FORMS */
144 |
145 | .aligned label {
146 | padding: 0 0 3px 1em;
147 | float: right;
148 | }
149 |
150 | .submit-row {
151 | text-align: left
152 | }
153 |
154 | .submit-row p.deletelink-box {
155 | float: right;
156 | }
157 |
158 | .submit-row .deletelink {
159 | background: url(../img/icon_deletelink.gif) 0 50% no-repeat;
160 | padding-right: 14px;
161 | }
162 |
163 | .vDateField, .vTimeField {
164 | margin-left: 2px;
165 | }
166 |
167 | form ul.inline li {
168 | float: right;
169 | padding-right: 0;
170 | padding-left: 7px;
171 | }
172 |
173 | input[type=submit].default, .submit-row input.default {
174 | float: left;
175 | }
176 |
177 | fieldset .field-box {
178 | float: right;
179 | margin-left: 20px;
180 | margin-right: 0;
181 | }
182 |
183 | .errorlist li {
184 | background-position: 100% .3em;
185 | padding: 4px 25px 4px 5px;
186 | }
187 |
188 | .errornote {
189 | background-position: 100% .3em;
190 | padding: 4px 25px 4px 5px;
191 | }
192 |
193 | /* WIDGETS */
194 |
195 | .calendarnav-previous {
196 | top: 0;
197 | left: auto;
198 | right: 0;
199 | }
200 |
201 | .calendarnav-next {
202 | top: 0;
203 | right: auto;
204 | left: 0;
205 | }
206 |
207 | .calendar caption, .calendarbox h2 {
208 | text-align: center;
209 | }
210 |
211 | .selector {
212 | float: right;
213 | }
214 |
215 | .selector .selector-filter {
216 | text-align: right;
217 | }
218 |
219 | .inline-deletelink {
220 | float: left;
221 | }
222 |
223 | /* MISC */
224 |
225 | .inline-related h2, .inline-group h2 {
226 | text-align: right
227 | }
228 |
229 | .inline-related h3 span.delete {
230 | padding-right: 20px;
231 | padding-left: inherit;
232 | left: 10px;
233 | right: inherit;
234 | float:left;
235 | }
236 |
237 | .inline-related h3 span.delete label {
238 | margin-left: inherit;
239 | margin-right: 2px;
240 | }
241 |
242 | /* IE7 specific bug fixes */
243 |
244 | div.colM {
245 | position: relative;
246 | }
247 |
248 | .submit-row input {
249 | float: left;
250 | }
251 |
--------------------------------------------------------------------------------
/static_in_env/static_root/admin/js/actions.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 | var lastChecked;
3 |
4 | $.fn.actions = function(opts) {
5 | var options = $.extend({}, $.fn.actions.defaults, opts);
6 | var actionCheckboxes = $(this);
7 | var list_editable_changed = false;
8 | var checker = function(checked) {
9 | if (checked) {
10 | showQuestion();
11 | } else {
12 | reset();
13 | }
14 | $(actionCheckboxes).prop("checked", checked)
15 | .parent().parent().toggleClass(options.selectedClass, checked);
16 | },
17 | updateCounter = function() {
18 | var sel = $(actionCheckboxes).filter(":checked").length;
19 | // _actions_icnt is defined in the generated HTML
20 | // and contains the total amount of objects in the queryset
21 | $(options.counterContainer).html(interpolate(
22 | ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), {
23 | sel: sel,
24 | cnt: _actions_icnt
25 | }, true));
26 | $(options.allToggle).prop("checked", function() {
27 | var value;
28 | if (sel == actionCheckboxes.length) {
29 | value = true;
30 | showQuestion();
31 | } else {
32 | value = false;
33 | clearAcross();
34 | }
35 | return value;
36 | });
37 | },
38 | showQuestion = function() {
39 | $(options.acrossClears).hide();
40 | $(options.acrossQuestions).show();
41 | $(options.allContainer).hide();
42 | },
43 | showClear = function() {
44 | $(options.acrossClears).show();
45 | $(options.acrossQuestions).hide();
46 | $(options.actionContainer).toggleClass(options.selectedClass);
47 | $(options.allContainer).show();
48 | $(options.counterContainer).hide();
49 | },
50 | reset = function() {
51 | $(options.acrossClears).hide();
52 | $(options.acrossQuestions).hide();
53 | $(options.allContainer).hide();
54 | $(options.counterContainer).show();
55 | },
56 | clearAcross = function() {
57 | reset();
58 | $(options.acrossInput).val(0);
59 | $(options.actionContainer).removeClass(options.selectedClass);
60 | };
61 | // Show counter by default
62 | $(options.counterContainer).show();
63 | // Check state of checkboxes and reinit state if needed
64 | $(this).filter(":checked").each(function(i) {
65 | $(this).parent().parent().toggleClass(options.selectedClass);
66 | updateCounter();
67 | if ($(options.acrossInput).val() == 1) {
68 | showClear();
69 | }
70 | });
71 | $(options.allToggle).show().click(function() {
72 | checker($(this).prop("checked"));
73 | updateCounter();
74 | });
75 | $("a", options.acrossQuestions).click(function(event) {
76 | event.preventDefault();
77 | $(options.acrossInput).val(1);
78 | showClear();
79 | });
80 | $("a", options.acrossClears).click(function(event) {
81 | event.preventDefault();
82 | $(options.allToggle).prop("checked", false);
83 | clearAcross();
84 | checker(0);
85 | updateCounter();
86 | });
87 | lastChecked = null;
88 | $(actionCheckboxes).click(function(event) {
89 | if (!event) { event = window.event; }
90 | var target = event.target ? event.target : event.srcElement;
91 | if (lastChecked && $.data(lastChecked) != $.data(target) && event.shiftKey === true) {
92 | var inrange = false;
93 | $(lastChecked).prop("checked", target.checked)
94 | .parent().parent().toggleClass(options.selectedClass, target.checked);
95 | $(actionCheckboxes).each(function() {
96 | if ($.data(this) == $.data(lastChecked) || $.data(this) == $.data(target)) {
97 | inrange = (inrange) ? false : true;
98 | }
99 | if (inrange) {
100 | $(this).prop("checked", target.checked)
101 | .parent().parent().toggleClass(options.selectedClass, target.checked);
102 | }
103 | });
104 | }
105 | $(target).parent().parent().toggleClass(options.selectedClass, target.checked);
106 | lastChecked = target;
107 | updateCounter();
108 | });
109 | $('form#changelist-form table#result_list tr').find('td:gt(0) :input').change(function() {
110 | list_editable_changed = true;
111 | });
112 | $('form#changelist-form button[name="index"]').click(function(event) {
113 | if (list_editable_changed) {
114 | return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."));
115 | }
116 | });
117 | $('form#changelist-form input[name="_save"]').click(function(event) {
118 | var action_changed = false;
119 | $('select option:selected', options.actionContainer).each(function() {
120 | if ($(this).val()) {
121 | action_changed = true;
122 | }
123 | });
124 | if (action_changed) {
125 | if (list_editable_changed) {
126 | return confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action."));
127 | } else {
128 | return confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button."));
129 | }
130 | }
131 | });
132 | };
133 | /* Setup plugin defaults */
134 | $.fn.actions.defaults = {
135 | actionContainer: "div.actions",
136 | counterContainer: "span.action-counter",
137 | allContainer: "div.actions span.all",
138 | acrossInput: "div.actions input.select-across",
139 | acrossQuestions: "div.actions span.question",
140 | acrossClears: "div.actions span.clear",
141 | allToggle: "#action-toggle",
142 | selectedClass: "selected"
143 | };
144 | })(django.jQuery);
145 |
--------------------------------------------------------------------------------
/src/products/models.py:
--------------------------------------------------------------------------------
1 | from django.core.urlresolvers import reverse
2 | from django.db import models
3 | from django.db.models.signals import post_save
4 | from django.utils.safestring import mark_safe
5 | from django.utils.text import slugify
6 |
7 | # Create your models here.
8 |
9 | class ProductQuerySet(models.query.QuerySet):
10 | def active(self):
11 | return self.filter(active=True)
12 |
13 |
14 | class ProductManager(models.Manager):
15 | def get_queryset(self):
16 | return ProductQuerySet(self.model, using=self._db)
17 |
18 | def all(self, *args, **kwargs):
19 | return self.get_queryset().active()
20 |
21 | def get_related(self, instance):
22 | products_one = self.get_queryset().filter(categories__in=instance.categories.all())
23 | products_two = self.get_queryset().filter(default=instance.default)
24 | qs = (products_one | products_two).exclude(id=instance.id).distinct()
25 | return qs
26 |
27 |
28 | class Product(models.Model):
29 | title = models.CharField(max_length=120)
30 | description = models.TextField(blank=True, null=True)
31 | price = models.DecimalField(decimal_places=2, max_digits=20)
32 | active = models.BooleanField(default=True)
33 | categories = models.ManyToManyField('Category', blank=True)
34 | default = models.ForeignKey('Category', related_name='default_category', null=True, blank=True)
35 |
36 | objects = ProductManager()
37 |
38 | class Meta:
39 | ordering = ["-title"]
40 |
41 | def __unicode__(self): #def __str__(self):
42 | return self.title
43 |
44 | def get_absolute_url(self):
45 | return reverse("product_detail", kwargs={"pk": self.pk})
46 |
47 | def get_image_url(self):
48 | img = self.productimage_set.first()
49 | if img:
50 | return img.image.url
51 | return img #None
52 |
53 |
54 |
55 |
56 | class Variation(models.Model):
57 | product = models.ForeignKey(Product)
58 | title = models.CharField(max_length=120)
59 | price = models.DecimalField(decimal_places=2, max_digits=20)
60 | sale_price = models.DecimalField(decimal_places=2, max_digits=20, null=True, blank=True)
61 | active = models.BooleanField(default=True)
62 | inventory = models.IntegerField(null=True, blank=True) #refer none == unlimited amount
63 |
64 | def __unicode__(self):
65 | return self.title
66 |
67 | def get_price(self):
68 | if self.sale_price is not None:
69 | return self.sale_price
70 | else:
71 | return self.price
72 |
73 | def get_html_price(self):
74 | if self.sale_price is not None:
75 | html_text = "%s %s " %(self.sale_price, self.price)
76 | else:
77 | html_text = "%s " %(self.price)
78 | return mark_safe(html_text)
79 |
80 | def get_absolute_url(self):
81 | return self.product.get_absolute_url()
82 |
83 | def add_to_cart(self):
84 | return "%s?item=%s&qty=1" %(reverse("cart"), self.id)
85 |
86 | def remove_from_cart(self):
87 | return "%s?item=%s&qty=1&delete=True" %(reverse("cart"), self.id)
88 |
89 | def get_title(self):
90 | return "%s - %s" %(self.product.title, self.title)
91 |
92 |
93 |
94 | def product_post_saved_receiver(sender, instance, created, *args, **kwargs):
95 | product = instance
96 | variations = product.variation_set.all()
97 | if variations.count() == 0:
98 | new_var = Variation()
99 | new_var.product = product
100 | new_var.title = "Default"
101 | new_var.price = product.price
102 | new_var.save()
103 |
104 |
105 | post_save.connect(product_post_saved_receiver, sender=Product)
106 |
107 |
108 | def image_upload_to(instance, filename):
109 | title = instance.product.title
110 | slug = slugify(title)
111 | basename, file_extension = filename.split(".")
112 | new_filename = "%s-%s.%s" %(slug, instance.id, file_extension)
113 | return "products/%s/%s" %(slug, new_filename)
114 |
115 |
116 | class ProductImage(models.Model):
117 | product = models.ForeignKey(Product)
118 | image = models.ImageField(upload_to=image_upload_to)
119 |
120 | def __unicode__(self):
121 | return self.product.title
122 |
123 | # Product Category
124 |
125 |
126 | class Category(models.Model):
127 | title = models.CharField(max_length=120, unique=True)
128 | slug = models.SlugField(unique=True)
129 | description = models.TextField(null=True, blank=True)
130 | active = models.BooleanField(default=True)
131 | timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
132 |
133 | def __unicode__(self):
134 | return self.title
135 |
136 |
137 | def get_absolute_url(self):
138 | return reverse("category_detail", kwargs={"slug": self.slug })
139 |
140 |
141 |
142 | def image_upload_to_featured(instance, filename):
143 | title = instance.product.title
144 | slug = slugify(title)
145 | basename, file_extension = filename.split(".")
146 | new_filename = "%s-%s.%s" %(slug, instance.id, file_extension)
147 | return "products/%s/featured/%s" %(slug, new_filename)
148 |
149 |
150 |
151 |
152 | class ProductFeatured(models.Model):
153 | product = models.ForeignKey(Product)
154 | image = models.ImageField(upload_to=image_upload_to_featured)
155 | title = models.CharField(max_length=120, null=True, blank=True)
156 | text = models.CharField(max_length=220, null=True, blank=True)
157 | text_right = models.BooleanField(default=False)
158 | text_css_color = models.CharField(max_length=6, null=True, blank=True)
159 | show_price = models.BooleanField(default=False)
160 | make_image_background = models.BooleanField(default=False)
161 | active = models.BooleanField(default=True)
162 |
163 | def __unicode__(self):
164 | return self.product.title
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
--------------------------------------------------------------------------------
/src/ecommerce2/settings/local.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for ecommerce2 project.
3 |
4 | Generated by 'django-admin startproject' using Django 1.8.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.8/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/1.8/ref/settings/
11 | """
12 |
13 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
14 | import datetime
15 | import os
16 |
17 |
18 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
19 | #root of project
20 |
21 | # Quick-start development settings - unsuitable for production
22 | # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
23 |
24 | # SECURITY WARNING: keep the secret key used in production secret!
25 | SECRET_KEY = 'csqwlmc8s55o($rt6ozh7u+ui9zb-et00w$d90j8$^!nvj41_r'
26 |
27 | # SECURITY WARNING: don't run with debug turned on in production!
28 | DEBUG = True
29 |
30 | ALLOWED_HOSTS = []
31 |
32 | EMAIL_HOST = 'smtp.gmail.com'
33 | EMAIL_HOST_USER = 'yourgmail@gmail.com'
34 | EMAIL_HOST_PASSWORD = 'yourpassword'
35 | EMAIL_PORT = 587
36 | EMAIL_USE_TLS = True
37 |
38 | '''
39 | If using gmail, you will need to
40 | unlock Captcha to enable Django
41 | to send for you:
42 | https://accounts.google.com/displayunlockcaptcha
43 | '''
44 |
45 |
46 |
47 | # Application definition
48 |
49 | INSTALLED_APPS = (
50 | #django app
51 | 'django.contrib.admin',
52 | 'django.contrib.auth',
53 | 'django.contrib.contenttypes',
54 | 'django.contrib.sessions',
55 | 'django.contrib.sites',
56 | 'django.contrib.messages',
57 | 'django.contrib.staticfiles',
58 | #third party apps
59 | 'corsheaders',
60 | 'crispy_forms',
61 | 'django_filters',
62 | 'registration',
63 | 'rest_framework',
64 | #my apps
65 | 'carts',
66 | 'newsletter',
67 | 'orders',
68 | 'products',
69 | )
70 |
71 | MIDDLEWARE_CLASSES = (
72 | 'django.contrib.sessions.middleware.SessionMiddleware',
73 | 'corsheaders.middleware.CorsMiddleware',
74 | 'django.middleware.common.CommonMiddleware',
75 | 'django.middleware.csrf.CsrfViewMiddleware',
76 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
77 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
78 | 'django.contrib.messages.middleware.MessageMiddleware',
79 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
80 | 'django.middleware.security.SecurityMiddleware',
81 | )
82 |
83 | # CORS_ORIGIN_WHITELIST = (
84 | # 'ang.heroku.com'
85 | # )
86 |
87 | # CORS_ORIGIN_REGEX_WHITELIST = ('^(https?://)?(\w+\.)?teamcfe\.com$', )
88 | CORS_URLS_REGEX = r'^/api/.*$'
89 |
90 |
91 |
92 | ROOT_URLCONF = 'ecommerce2.urls'
93 |
94 | TEMPLATES = [
95 | {
96 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
97 | 'DIRS': [os.path.join(BASE_DIR, "templates")],
98 | 'APP_DIRS': True,
99 | 'OPTIONS': {
100 | 'context_processors': [
101 | 'django.template.context_processors.debug',
102 | 'django.template.context_processors.request',
103 | 'django.contrib.auth.context_processors.auth',
104 | 'django.contrib.messages.context_processors.messages',
105 | ],
106 | },
107 | },
108 | ]
109 |
110 | WSGI_APPLICATION = 'ecommerce2.wsgi.application'
111 |
112 |
113 | # Database
114 | # https://docs.djangoproject.com/en/1.8/ref/settings/#databases
115 |
116 | DATABASES = {
117 | 'default': {
118 | 'ENGINE': 'django.db.backends.sqlite3',
119 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
120 | }
121 | }
122 |
123 |
124 | # Internationalization
125 | # https://docs.djangoproject.com/en/1.8/topics/i18n/
126 |
127 | LANGUAGE_CODE = 'en-us'
128 |
129 | TIME_ZONE = 'UTC'
130 |
131 | USE_I18N = True
132 |
133 | USE_L10N = True
134 |
135 | USE_TZ = True
136 |
137 |
138 | # Static files (CSS, JavaScript, Images)
139 | # https://docs.djangoproject.com/en/1.8/howto/static-files/
140 |
141 | STATIC_URL = '/static/'
142 |
143 | STATIC_ROOT = os.path.join(os.path.dirname(BASE_DIR), "static_in_env", "static_root")
144 |
145 | STATICFILES_DIRS = (
146 | os.path.join(BASE_DIR, "static_in_pro", "our_static"),
147 | #os.path.join(BASE_DIR, "static_in_env"),
148 | #'/var/www/static/',
149 | )
150 |
151 | MEDIA_URL = '/media/'
152 | MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), "static_in_env", "media_root")
153 |
154 |
155 |
156 | #Crispy FORM TAGs SETTINGS
157 | CRISPY_TEMPLATE_PACK = 'bootstrap3'
158 |
159 |
160 | #DJANGO REGISTRATION REDUX SETTINGS
161 | ACCOUNT_ACTIVATION_DAYS = 7
162 | REGISTRATION_AUTO_LOGIN = True
163 | SITE_ID = 1
164 | LOGIN_REDIRECT_URL = '/'
165 |
166 |
167 | #Braintree Payments Details
168 | BRAINTREE_PUBLIC = "qn3p5n7njksw47r3"
169 | BRAINTREE_PRIVATE = "d14ac944794c0df1c81991ecf49221ff"
170 | BRAINTREE_MERCHANT_ID = "n84nynknvzz3j3sz"
171 | BRAINTREE_ENVIRONEMNT = "Sandbox"
172 |
173 |
174 |
175 | REST_FRAMEWORK = {
176 | 'DEFAULT_PERMISSION_CLASSES': (
177 | 'rest_framework.permissions.AllowAny',
178 | ),
179 | 'DEFAULT_AUTHENTICATION_CLASSES': (
180 | #'rest_framework.authentication.BasicAuthentication',
181 | #'rest_framework.authentication.SessionAuthentication',
182 | "rest_framework_jwt.authentication.JSONWebTokenAuthentication",
183 | ),
184 | 'DEFAULT_PAGINATION_CLASS': 'products.pagination.ProductPagination',
185 | "SEARCH_PARAM" : "q"
186 | }
187 |
188 | JWT_AUTH = {
189 | "JWT_RESPONSE_PAYLOAD_HANDLER":
190 | "ecommerce2.utils.jwt_response_payload_handler",
191 | "JWT_EXPIRATION_DELTA": datetime.timedelta(seconds=30000),
192 | "JWT_ALLOW_REFRESH": True, #False
193 | }
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
--------------------------------------------------------------------------------