├── .gitignore ├── POSTGRESQL.md ├── README.md ├── requirements.txt └── restshop_project ├── manage.py ├── restshop ├── __init__.py ├── admin.py ├── admin_models.py ├── api │ ├── __init__.py │ ├── cart │ │ ├── __init__.py │ │ ├── models.py │ │ ├── serializers.py │ │ ├── urls.py │ │ └── views.py │ ├── order │ │ ├── __init__.py │ │ ├── models.py │ │ ├── serializers.py │ │ ├── urls.py │ │ └── views.py │ ├── order_unit │ │ ├── __init__.py │ │ ├── models.py │ │ ├── serializers.py │ │ ├── urls.py │ │ └── views.py │ ├── product │ │ ├── __init__.py │ │ ├── models.py │ │ ├── serializers.py │ │ ├── urls.py │ │ └── views.py │ ├── property │ │ ├── __init__.py │ │ ├── models.py │ │ ├── serializers.py │ │ ├── urls.py │ │ └── views.py │ ├── tag │ │ ├── __init__.py │ │ ├── models.py │ │ ├── serializers.py │ │ ├── urls.py │ │ └── views.py │ ├── unit │ │ ├── __init__.py │ │ ├── models.py │ │ ├── serializers.py │ │ ├── urls.py │ │ └── views.py │ └── user │ │ ├── __init__.py │ │ ├── models.py │ │ ├── serializers.py │ │ ├── service.py │ │ ├── urls.py │ │ └── views.py ├── apps.py ├── fixtures │ ├── .gitignore │ ├── fixture_creator.py │ ├── process_raw.py │ └── products │ │ ├── products │ │ ├── __init__.py │ │ ├── items.py │ │ ├── middlewares.py │ │ ├── pipelines.py │ │ ├── settings.py │ │ └── spiders │ │ │ ├── __init__.py │ │ │ └── nike.py │ │ └── scrapy.cfg ├── middleware.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20170826_1853.py │ ├── 0003_auto_20170827_0812.py │ ├── 0004_auto_20170828_1204.py │ ├── 0005_productimage_is_main.py │ ├── 0006_auto_20170828_1321.py │ ├── 0007_order.py │ ├── 0008_auto_20170828_1404.py │ ├── 0009_order_address.py │ ├── 0010_auto_20170829_0742.py │ ├── 0011_product_seller.py │ ├── 0012_auto_20170829_0950.py │ ├── 0013_auto_20170829_1012.py │ ├── 0014_auto_20170829_1058.py │ ├── 0015_auto_20170830_0720.py │ ├── 0016_auto_20170830_0724.py │ ├── 0017_remove_order_unit_set.py │ ├── 0018_auto_20170830_0725.py │ ├── 0019_order_user.py │ ├── 0020_auto_20170904_0608.py │ ├── 0021_auto_20170904_0830.py │ ├── 0022_auto_20170905_0554.py │ ├── 0023_auto_20170905_0755.py │ ├── 0024_auto_20170905_1512.py │ ├── 0025_product_description.py │ ├── 0026_auto_20171017_1134.py │ ├── 0027_auto_20171017_1305.py │ ├── 0028_auto_20171018_1022.py │ ├── 0029_auto_20171123_1218.py │ └── __init__.py ├── models.py ├── tests.py └── urls.py ├── restshop_project ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py ├── scrape_nike.sh └── ui ├── .babelrc ├── .bowerrc ├── .gitignore ├── .jshintrc ├── .travis.yml ├── LICENSE ├── app ├── img │ ├── .gitkeep │ ├── empty.png │ ├── favicon.png │ ├── logo.png │ └── spinner.gif ├── index.html ├── js │ ├── app.js │ ├── controllers │ │ ├── cart.controller.js │ │ ├── header.controller.js │ │ ├── login.controller.js │ │ ├── product-details.controller.js │ │ ├── product-list.controller.js │ │ ├── profile.controller.js │ │ ├── profile.deliveryinfo.controller.js │ │ ├── profile.info.controller.js │ │ ├── profile.order-details.controller.js │ │ ├── profile.order-list.controller.js │ │ └── signup.controller.js │ ├── directives │ │ ├── cart-order-unit.directive.js │ │ ├── filter-options.directive.js │ │ ├── product-list-item.directive.js │ │ └── pwd-check.directive.js │ └── services │ │ ├── api.service.js │ │ ├── config.js │ │ ├── data-services │ │ ├── cart-order-data.service.js │ │ ├── product-data.service.js │ │ └── user-data.service.js │ │ ├── notifier.service.js │ │ └── properties-tags.service.js ├── partials │ ├── .gitkeep │ ├── cart-order-unit.html │ ├── cart.html │ ├── filter-options.html │ ├── header.html │ ├── login.html │ ├── notifier.html │ ├── product-details.html │ ├── product-list-item.html │ ├── product-list.html │ ├── profile.delivery-info.html │ ├── profile.html │ ├── profile.info.html │ ├── profile.order-details.html │ ├── profile.order-list.html │ └── signup.html └── styles │ ├── .gitkeep │ ├── main.scss │ ├── mixins.scss │ ├── partials │ ├── app.scss │ ├── cart-order-unit.scss │ ├── cart.scss │ ├── checkboxes.scss │ ├── filter-options.scss │ ├── header.scss │ ├── login.scss │ ├── notifier.scss │ ├── price-slider.scss │ ├── product-details.scss │ ├── product-list-item.scss │ └── profile.scss │ └── variables.scss ├── bower.json ├── gulpfile.babel.js ├── package.json └── test ├── e2e └── scenarios.js ├── karma.conf.js ├── protractor-conf.js └── unit ├── controllersSpec.js ├── directivesSpec.js ├── filtersSpec.js └── servicesSpec.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | # PyCharm 104 | .idea 105 | 106 | # Media files 107 | media/ 108 | -------------------------------------------------------------------------------- /POSTGRESQL.md: -------------------------------------------------------------------------------- 1 | ## PostgreSQL 2 | 3 | The following apt commands will get you the packages you need: 4 | 5 | ``` 6 | sudo apt-get update 7 | sudo apt-get install python-pip python-dev libpq-dev postgresql postgresql-contrib 8 | ``` 9 | 10 | During the Postgres installation, an operating system user named postgres was created to correspond to the postgres PostgreSQL administrative user. 11 | We need to change to this user to perform administrative tasks: 12 | ``` 13 | sudo su - postgres 14 | ``` 15 | 16 | You should now be in a shell session for the postgres user. Log into a Postgres session by typing: 17 | ``` 18 | psql 19 | ``` 20 | 21 | Create database `restshop`: 22 | ``` 23 | CREATE DATABASE restshop; 24 | ``` 25 | 26 | Create a database user: 27 | ``` 28 | CREATE USER restshopuser WITH PASSWORD '12345678'; 29 | ``` 30 | 31 | Change user settings: 32 | ``` 33 | ALTER ROLE restshopuser SET client_encoding TO 'utf8'; 34 | ALTER ROLE restshopuser SET default_transaction_isolation TO 'read committed'; 35 | ALTER ROLE restshopuser SET timezone TO 'UTC'; 36 | ALTER ROLE restshopuser CREATEDB; 37 | ``` 38 | 39 | Now, all we need to do is give our database user access rights to the database we created: 40 | ``` 41 | GRANT ALL PRIVILEGES ON DATABASE restshop TO restshopuser; 42 | ``` 43 | 44 | Exit the SQL prompt to get back to the postgres user's shell session: 45 | ``` 46 | \q 47 | ``` 48 | 49 | Exit out of the postgres user's shell session to get back to your regular user's shell session: 50 | ``` 51 | exit 52 | ``` 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rest-Shop 2 | 3 | Training e-commerce with Python 3 and Angular. 4 | 5 | ## Installation 6 | 7 | Create [virtual environment](https://virtualenv.pypa.io/en/stable/) (preferably): 8 | ``` 9 | virtualenv -p python3 restshop 10 | ``` 11 | 12 | Install dependencies (make sure you are inside your virtual environment): 13 | ``` 14 | pip install -r requirements.txt 15 | ``` 16 | 17 | Install PostgreSQL: [instructions here](POSTGRESQL.md). 18 | 19 | Set environment variables: 20 | ``` 21 | export DB_USER=YOUR_DB_USERNAME 22 | export DB_PASSWORD=YOUR_DB_PASSWORD 23 | ``` 24 | 25 | Set up database schema: 26 | ``` 27 | python manage.py migrate 28 | ``` 29 | 30 | Create superuser: 31 | ``` 32 | python manage.py createsuperuser 33 | ``` 34 | 35 | ## Frontend Setting 36 | 37 | Install Node.js: 38 | ``` 39 | curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash - 40 | sudo apt-get install -y nodejs 41 | ``` 42 | 43 | Go to `ui` folder: 44 | ``` 45 | cd ui/ 46 | ``` 47 | 48 | Install nodejs dependencies: 49 | ``` 50 | npm install 51 | ``` 52 | 53 | Install gulp (for frontend building): 54 | ``` 55 | npm install -g gulp-cli 56 | ``` 57 | 58 | Install project dependencies: 59 | ``` 60 | bower install 61 | ``` 62 | 63 | Build frontend: 64 | ``` 65 | gulp 66 | ``` 67 | 68 | For development, run with `--env=dev`: 69 | ``` 70 | gulp --env=dev 71 | ``` 72 | 73 | ## Populating database 74 | 75 | If you want to automatically populate database with data, 76 | you need to get it from a website with a scraper. 77 | 78 | You can use a small bash script for that purpose: 79 | ``` 80 | ./scrape_nike.sh 81 | ``` 82 | 83 | 84 | ## Running 85 | 86 | Run server: 87 | ``` 88 | python manage.py runserver 89 | ``` 90 | 91 | Go to http://localhost:8000 and start using app. 92 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asn1crypto==0.22.0 2 | attrs==17.2.0 3 | Automat==0.6.0 4 | certifi==2017.7.27.1 5 | cffi==1.11.0 6 | chardet==3.0.4 7 | constantly==15.1.0 8 | coreapi==2.3.1 9 | coreschema==0.0.4 10 | cryptography==2.0.3 11 | cssselect==1.0.1 12 | decorator==4.1.2 13 | Django==1.11.4 14 | django-cors-headers==2.1.0 15 | django-crispy-forms==1.6.1 16 | django-filter==1.0.4 17 | djangorestframework==3.6.4 18 | djangorestframework-sav==0.1.0 19 | hyperlink==17.3.1 20 | idna==2.6 21 | incremental==17.5.0 22 | ipython==6.1.0 23 | ipython-genutils==0.2.0 24 | itypes==1.1.0 25 | jedi==0.10.2 26 | Jinja2==2.9.6 27 | lxml==4.0.0 28 | Markdown==2.6.9 29 | MarkupSafe==1.0 30 | olefile==0.44 31 | parsel==1.2.0 32 | pexpect==4.2.1 33 | pickleshare==0.7.4 34 | Pillow==4.2.1 35 | prompt-toolkit==1.0.15 36 | psycopg2==2.7.3 37 | ptyprocess==0.5.2 38 | pyasn1==0.3.6 39 | pyasn1-modules==0.1.4 40 | pycparser==2.18 41 | PyDispatcher==2.0.5 42 | Pygments==2.2.0 43 | pyOpenSSL==17.3.0 44 | pytz==2017.2 45 | queuelib==1.4.2 46 | requests==2.18.4 47 | Scrapy==1.4.0 48 | service-identity==17.0.0 49 | simplegeneric==0.8.1 50 | six==1.10.0 51 | traitlets==4.3.2 52 | Twisted==17.5.0 53 | uritemplate==3.0.0 54 | urllib3==1.22 55 | w3lib==1.18.0 56 | wcwidth==0.1.7 57 | zope.interface==4.4.3 58 | -------------------------------------------------------------------------------- /restshop_project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "restshop_project.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /restshop_project/restshop/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/restshop/__init__.py -------------------------------------------------------------------------------- /restshop_project/restshop/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from restshop.api.order.models import Order 4 | from restshop.api.order_unit.models import OrderUnit 5 | from restshop.api.product.models import Product 6 | from restshop.api.property.models import PropertyValue, Property 7 | from restshop.api.tag.models import Tag 8 | from restshop.api.unit.models import Unit, UnitImage 9 | from restshop.api.user.models import Seller 10 | from .admin_models import UnitAdmin, ProductAdmin, UnitImageAdmin, OrderAdmin, OrderUnitAdmin, PropertyAdmin, \ 11 | PropertyValueAdmin 12 | 13 | admin.site.register([Seller, 14 | Tag]) 15 | 16 | admin.site.register(PropertyValue, PropertyValueAdmin) 17 | admin.site.register(Property, PropertyAdmin) 18 | admin.site.register(Unit, UnitAdmin) 19 | admin.site.register(Product, ProductAdmin) 20 | admin.site.register(UnitImage, UnitImageAdmin) 21 | admin.site.register(Order, OrderAdmin) 22 | admin.site.register(OrderUnit, OrderUnitAdmin) 23 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/restshop/api/__init__.py -------------------------------------------------------------------------------- /restshop_project/restshop/api/cart/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/restshop/api/cart/__init__.py -------------------------------------------------------------------------------- /restshop_project/restshop/api/cart/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.contrib.sessions.models import Session 3 | from django.db import models 4 | 5 | from restshop.api.unit.models import Unit 6 | 7 | 8 | class CartUnit(models.Model): 9 | user = models.ForeignKey(to=User, null=True, on_delete=models.CASCADE, related_name='cart_units') 10 | session = models.ForeignKey(to=Session, null=True, on_delete=models.CASCADE, related_name='cart_units') 11 | unit = models.ForeignKey(to=Unit) 12 | quantity = models.PositiveIntegerField() 13 | 14 | def __str__(self): 15 | return '{} unit(s) of {}'.format(self.quantity, self.unit) 16 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/cart/serializers.py: -------------------------------------------------------------------------------- 1 | from django.core.exceptions import ObjectDoesNotExist 2 | from rest_framework import serializers 3 | 4 | from restshop.api.order_unit.models import OrderUnit 5 | from restshop.api.unit.models import Unit 6 | from restshop.api.unit.serializers import UnitForOrderDetail 7 | 8 | 9 | class CartUnitSerializer(serializers.Serializer): 10 | sku = serializers.CharField(write_only=True) 11 | quantity = serializers.IntegerField(default=1, min_value=1) 12 | unit = UnitForOrderDetail(read_only=True) 13 | 14 | class Meta: 15 | model = OrderUnit 16 | fields = ('sku', 'quantity', 'unit') 17 | 18 | def validate(self, data): 19 | sku = data['sku'] 20 | quantity = data['quantity'] 21 | 22 | try: 23 | unit = Unit.objects.get(sku=sku) 24 | except ObjectDoesNotExist: 25 | raise serializers.ValidationError('Unit does not exist') 26 | 27 | if unit.num_in_stock < quantity: 28 | raise serializers.ValidationError('There are not enough units in stock') 29 | 30 | return data 31 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/cart/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from restshop.api.cart.views import CartView, CartUnitView 4 | 5 | urlpatterns = [ 6 | url(r'^cart/$', CartView.as_view(), name='cart'), 7 | url(r'^cart/(?P[A-Za-z\-_0-9]+)/$', CartUnitView.as_view(), name='cart-unit'), 8 | ] 9 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/cart/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.sessions.models import Session 2 | from rest_framework import status 3 | from rest_framework.permissions import AllowAny 4 | from rest_framework.response import Response 5 | from rest_framework.views import APIView 6 | 7 | from restshop.api.cart.models import CartUnit 8 | from restshop.api.cart.serializers import CartUnitSerializer 9 | from restshop.api.unit.models import Unit 10 | 11 | 12 | class CartView(APIView): 13 | permission_classes = (AllowAny,) 14 | 15 | def get(self, request): 16 | if not bool(request.user.is_anonymous): 17 | cart_units = request.user.cart_units.all() 18 | else: 19 | if request.session.session_key is None: 20 | request.session.save() 21 | 22 | cart_units = Session.objects.get(session_key=request.session.session_key).cart_units.all() 23 | 24 | return Response(CartUnitSerializer(cart_units.order_by('unit__sku'), many=True).data, status=status.HTTP_200_OK) 25 | 26 | def post(self, request): 27 | serializer = CartUnitSerializer(data=request.data) 28 | serializer.is_valid(raise_exception=True) 29 | 30 | data = serializer.validated_data 31 | 32 | unit = Unit.objects.get(sku=data['sku']) 33 | 34 | cart_unit_data = { 35 | 'unit': unit, 36 | 'user': None, 37 | 'session': None 38 | } 39 | 40 | if not bool(request.user.is_anonymous): 41 | cart_unit_data['user'] = request.user 42 | else: 43 | if request.session.session_key is None: 44 | request.session.save() 45 | 46 | cart_unit_data['session'] = Session.objects.get(session_key=request.session.session_key) 47 | 48 | cart_unit = CartUnit.objects.filter(**cart_unit_data).first() 49 | 50 | if cart_unit is None: 51 | cart_unit = CartUnit(**cart_unit_data) 52 | 53 | cart_unit.quantity = data['quantity'] 54 | cart_unit.save() 55 | 56 | return Response(status=status.HTTP_201_CREATED) 57 | 58 | 59 | class CartUnitView(APIView): 60 | permission_classes = (AllowAny,) 61 | 62 | def delete(self, request, sku=None): 63 | if not bool(request.user.is_anonymous): 64 | cart_units = request.user.cart_units.all() 65 | else: 66 | if request.session.session_key is None: 67 | request.session.save() 68 | 69 | cart_units = Session.objects.get(session_key=request.session.session_key).cart_units.all() 70 | 71 | cart_unit = cart_units.filter(unit__sku=sku).first() 72 | 73 | if cart_unit is None: 74 | return Response(status=status.HTTP_400_BAD_REQUEST) 75 | 76 | cart_unit.delete() 77 | 78 | return Response(status=status.HTTP_200_OK) 79 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/order/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/restshop/api/order/__init__.py -------------------------------------------------------------------------------- /restshop_project/restshop/api/order/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.db import models 3 | 4 | from restshop.api.unit.models import Unit 5 | 6 | 7 | class Order(models.Model): 8 | created_at = models.DateTimeField(auto_now_add=True) 9 | unit_set = models.ManyToManyField(to=Unit, through='OrderUnit') 10 | user = models.ForeignKey(to=User, null=True, on_delete=models.SET_NULL) 11 | name = models.CharField(max_length=255) 12 | address = models.CharField(max_length=255) 13 | phone = models.CharField(max_length=31) 14 | 15 | class Meta: 16 | ordering = ['-created_at'] 17 | 18 | def __str__(self): 19 | items_count = self.unit_set.all().count() 20 | return 'Order ({} items) by {}'.format(items_count, self.name) 21 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/order/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from restshop.api.order.models import Order 4 | from restshop.api.order_unit.serializers import OrderUnitSerializer 5 | 6 | 7 | class OrderSerializer(serializers.ModelSerializer): 8 | 9 | class Meta: 10 | model = Order 11 | fields = ('name', 'address', 'phone') 12 | 13 | 14 | class OrderListSerializer(serializers.ModelSerializer): 15 | units_num = serializers.SerializerMethodField() 16 | 17 | class Meta: 18 | model = Order 19 | fields = ('id', 'created_at', 'units_num') 20 | 21 | def get_units_num(self, obj): 22 | return obj.unit_set.count() 23 | 24 | 25 | class OrderDetailSerializer(serializers.ModelSerializer): 26 | units = OrderUnitSerializer( 27 | many=True, 28 | read_only=True, 29 | source='orderunit_set' 30 | ) 31 | 32 | class Meta: 33 | model = Order 34 | fields = ('id', 'created_at', 'name', 'address', 'phone', 'units') 35 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/order/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from restshop.api.order.views import OrderView, OrderDetailView 4 | 5 | urlpatterns = [ 6 | url(r'^orders/$', OrderView.as_view(), name='order-list'), 7 | url(r'^orders/(?P[0-9]+)/$', OrderDetailView.as_view(), name='order-detail'), 8 | ] 9 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/order/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.sessions.models import Session 2 | from rest_framework import status, serializers 3 | from rest_framework.permissions import AllowAny, IsAuthenticated 4 | from rest_framework.response import Response 5 | from rest_framework.views import APIView 6 | 7 | from restshop.api.order.models import Order 8 | from restshop.api.order.serializers import OrderListSerializer, OrderSerializer, OrderDetailSerializer 9 | from restshop.api.order_unit.models import OrderUnit 10 | from restshop.api.user.models import DeliveryInfo 11 | from restshop.api.user.service import DeliveryInfoService 12 | 13 | 14 | class OrderView(APIView): 15 | permission_classes = (AllowAny,) 16 | 17 | def get(self, request): 18 | user = request.user 19 | 20 | if user.is_anonymous: 21 | return Response(status=status.HTTP_401_UNAUTHORIZED) 22 | 23 | queryset = Order.objects.filter(user=user) 24 | serializer = OrderListSerializer(queryset, many=True) 25 | return Response(serializer.data) 26 | 27 | def post(self, request): 28 | serializer = OrderSerializer(data=request.data) 29 | serializer.is_valid(raise_exception=True) 30 | 31 | data = serializer.data 32 | 33 | if not bool(request.user.is_anonymous): 34 | cart_units = request.user.cart_units.all() 35 | data['user'] = request.user 36 | 37 | DeliveryInfoService.delete_by_user(request.user) 38 | DeliveryInfo.objects.create(**data) 39 | else: 40 | if request.session.session_key is None: 41 | request.session.save() 42 | 43 | cart_units = Session.objects.get(session_key=request.session.session_key).cart_units.all() 44 | data['user'] = None 45 | 46 | if cart_units.count() == 0: 47 | raise serializers.ValidationError('Cart is empty, nothing to order') 48 | 49 | order = Order.objects.create(**data) 50 | 51 | for cart_unit in cart_units: 52 | if cart_unit.unit.num_in_stock < cart_unit.quantity: 53 | raise serializers.ValidationError( 54 | 'Not enough units in stock: {}'.format(cart_unit.unit.sku) 55 | ) 56 | 57 | for cart_unit in cart_units: 58 | unit = cart_unit.unit 59 | 60 | OrderUnit.objects.create( 61 | order=order, 62 | quantity=cart_unit.quantity, 63 | unit=unit, 64 | unit_price=unit.price 65 | ) 66 | 67 | unit.num_in_stock -= cart_unit.quantity 68 | unit.save() 69 | 70 | # Clear cart 71 | cart_unit.delete() 72 | 73 | return Response(OrderDetailSerializer(order).data, status=status.HTTP_201_CREATED) 74 | 75 | 76 | class OrderDetailView(APIView): 77 | permission_classes = (IsAuthenticated,) 78 | 79 | def get(self, request, pk=None): 80 | user = request.user 81 | order = Order.objects.get(pk=pk) 82 | 83 | if user.id != order.user.id: 84 | return Response(status=status.HTTP_401_UNAUTHORIZED) 85 | 86 | serializer = OrderDetailSerializer(order) 87 | return Response(serializer.data) 88 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/order_unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/restshop/api/order_unit/__init__.py -------------------------------------------------------------------------------- /restshop_project/restshop/api/order_unit/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from restshop.api.order.models import Order 4 | from restshop.api.unit.models import Unit 5 | 6 | 7 | class OrderUnit(models.Model): 8 | PENDING = 'PE' 9 | REJECTED = 'RE' 10 | COMPLETED = 'CO' 11 | STATUSES = ( 12 | (PENDING, 'Pending'), 13 | (REJECTED, 'Rejected'), 14 | (COMPLETED, 'Completed'), 15 | ) 16 | 17 | order = models.ForeignKey(to=Order, on_delete=models.CASCADE) 18 | unit = models.ForeignKey(to=Unit) 19 | quantity = models.PositiveIntegerField() 20 | unit_price = models.PositiveIntegerField() 21 | status = models.CharField(max_length=2, choices=STATUSES, default=PENDING) 22 | 23 | def __str__(self): 24 | return '{} pcs of {} by {}'.format(self.quantity, self.unit.product, self.order.name) 25 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/order_unit/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from restshop.api.order_unit.models import OrderUnit 4 | from restshop.api.unit.serializers import UnitForOrderDetail 5 | 6 | 7 | class OrderUnitSerializer(serializers.ModelSerializer): 8 | unit = serializers.SerializerMethodField() 9 | status = serializers.SerializerMethodField() 10 | 11 | class Meta: 12 | model = OrderUnit 13 | fields = ('quantity', 'status', 'unit') 14 | 15 | def get_unit(self, obj): 16 | data = UnitForOrderDetail(obj.unit).data 17 | 18 | # Use the price that was at the moment of purchase instead of current price. 19 | data['price'] = obj.unit_price 20 | 21 | return data 22 | 23 | def get_status(self, obj): 24 | return obj.get_status_display() 25 | 26 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/order_unit/urls.py: -------------------------------------------------------------------------------- 1 | 2 | urlpatterns = [ 3 | 4 | ] 5 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/order_unit/views.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/restshop/api/order_unit/views.py -------------------------------------------------------------------------------- /restshop_project/restshop/api/product/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/restshop/api/product/__init__.py -------------------------------------------------------------------------------- /restshop_project/restshop/api/product/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from restshop.api.tag.models import Tag 4 | from restshop.api.user.models import Seller 5 | 6 | 7 | class Product(models.Model): 8 | tag_set = models.ManyToManyField(to=Tag) 9 | seller = models.ForeignKey(to=Seller, on_delete=models.CASCADE) 10 | title = models.CharField(max_length=255) 11 | description = models.TextField() 12 | 13 | class Meta: 14 | ordering = ['title'] 15 | 16 | def __str__(self): 17 | return self.title 18 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/product/serializers.py: -------------------------------------------------------------------------------- 1 | from django.db.models import Max, Min 2 | from rest_framework import serializers 3 | 4 | from restshop.api.product.models import Product 5 | from restshop.api.tag.serializers import TagSerializer 6 | from restshop.api.unit.serializers import UnitSerializer 7 | 8 | 9 | class ProductListSerializer(serializers.ModelSerializer): 10 | tags = TagSerializer( 11 | many=True, 12 | read_only=True, 13 | source='tag_set' 14 | ) 15 | prices = serializers.SerializerMethodField() 16 | image = serializers.SerializerMethodField() 17 | 18 | class Meta: 19 | model = Product 20 | fields = ('id', 'title', 'tags', 'prices', 'image') 21 | 22 | def get_prices(self, obj): 23 | prices = obj.unit_set.aggregate(max=Max('price'), min=Min('price')) 24 | return { 25 | 'min': prices['min'], 26 | 'max': prices['max'] 27 | } 28 | 29 | def get_image(self, obj): 30 | unit = obj.unit_set.filter(unitimage__isnull=False).first() 31 | 32 | # If there are no units for the product: 33 | if unit is None: 34 | return None 35 | 36 | # Get image (preferably, main one) for the unit. 37 | image = unit.unitimage_set.order_by('-is_main').first() 38 | 39 | # If there are no images for the unit: 40 | if image is None: 41 | return None 42 | 43 | return image.image.url 44 | 45 | 46 | # Inherit from list serializer to get tags field. 47 | class ProductSerializer(ProductListSerializer): 48 | units = UnitSerializer( 49 | many=True, 50 | read_only=True, 51 | source='unit_set' 52 | ) 53 | 54 | class Meta: 55 | model = Product 56 | fields = ('id', 'title', 'tags', 'description', 'units') 57 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/product/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from restshop.api.product.views import ProductListView, ProductDetailView 4 | 5 | urlpatterns = [ 6 | url(r'^products/$', ProductListView.as_view(), name='product-list'), 7 | url(r'^products/(?P[0-9]+)/$', ProductDetailView.as_view(), name='product-detail'), 8 | ] 9 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/product/views.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | from django.db.models import Max, Min 4 | from rest_framework import generics 5 | from rest_framework.pagination import PageNumberPagination 6 | from rest_framework.response import Response 7 | 8 | from restshop.api.product.models import Product 9 | from restshop.api.product.serializers import ProductListSerializer, ProductSerializer 10 | from restshop.api.property.models import PropertyValue 11 | from restshop.api.unit.models import Unit 12 | 13 | 14 | class ProductSetPagination(PageNumberPagination): 15 | page_size = 32 16 | 17 | def get_paginated_response(self, data): 18 | prices = Unit.objects.aggregate(max=Max('price'), min=Min('price')) 19 | return Response({ 20 | 'meta': { 21 | 'page': self.page.number, 22 | 'has_prev': self.page.has_previous(), 23 | 'has_next': self.page.has_next(), 24 | 'min_price': prices['min'], 25 | 'max_price': prices['max'], 26 | }, 27 | 'data': data 28 | }) 29 | 30 | 31 | class ProductListView(generics.ListAPIView): 32 | serializer_class = ProductListSerializer 33 | pagination_class = ProductSetPagination 34 | 35 | def get_queryset(self): 36 | queryset = Product.objects.all() 37 | 38 | q = self.request.query_params.get('q', None) 39 | tags = self.request.query_params.get('tags') 40 | criteria = self.request.query_params.get('properties') 41 | in_stock = self.request.query_params.get('in_stock', None) 42 | price_min = self.request.query_params.get('price_min', None) 43 | price_max = self.request.query_params.get('price_max', None) 44 | 45 | if q is not None: 46 | queryset = queryset.filter(title__icontains=q) 47 | 48 | if tags: 49 | tags = tags.split(',') 50 | 51 | for tag in tags: 52 | queryset = queryset.filter(tag_set__name__iexact=tag).distinct() 53 | 54 | if criteria: 55 | criteria = criteria.split(',') 56 | values = PropertyValue.objects.filter(id__in=criteria) 57 | 58 | grouped_values = defaultdict(list) 59 | for value in values: 60 | grouped_values[value.property_id].append(value.id) 61 | 62 | for key in grouped_values: 63 | values = grouped_values[key] 64 | queryset = queryset.filter(unit__value_set__in=values).distinct() 65 | 66 | if in_stock == '1': 67 | queryset = queryset.filter(unit__num_in_stock__gt=0).distinct() 68 | 69 | if price_min is not None and price_min.isdigit(): 70 | queryset = queryset.filter(unit__price__gte=int(price_min)).distinct() 71 | 72 | if price_max is not None and price_max.isdigit(): 73 | queryset = queryset.filter(unit__price__lte=int(price_max)).distinct() 74 | 75 | return queryset 76 | 77 | 78 | class ProductDetailView(generics.RetrieveAPIView): 79 | queryset = Product.objects.all() 80 | serializer_class = ProductSerializer 81 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/property/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/restshop/api/property/__init__.py -------------------------------------------------------------------------------- /restshop_project/restshop/api/property/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Property(models.Model): 5 | name = models.CharField(max_length=255, unique=True) 6 | 7 | def __str__(self): 8 | return self.name 9 | 10 | class Meta: 11 | verbose_name_plural = 'properties' 12 | 13 | 14 | class PropertyValue(models.Model): 15 | value = models.CharField(max_length=255) 16 | property = models.ForeignKey(to=Property, on_delete=models.CASCADE) 17 | 18 | class Meta: 19 | ordering = ['property__name', 'value'] 20 | 21 | def __str__(self): 22 | return '{}: {}'.format(self.property.name, self.value) 23 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/property/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from restshop.api.property.models import Property 4 | 5 | 6 | class PropertySerializer(serializers.ModelSerializer): 7 | 8 | class Meta: 9 | model = Property 10 | 11 | def to_representation(self, instance): 12 | return { 13 | 'name': instance.name, 14 | 'values': [{ 15 | 'id': value.id, 16 | 'value': value.value 17 | } for value in instance.propertyvalue_set.all()] 18 | } 19 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/property/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from restshop.api.property.views import PropertyListView 4 | 5 | urlpatterns = [ 6 | url(r'^properties/$', PropertyListView.as_view(), name='property-list'), 7 | ] 8 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/property/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import generics 2 | 3 | from restshop.api.property.models import Property 4 | from restshop.api.property.serializers import PropertySerializer 5 | 6 | 7 | class PropertyListView(generics.ListAPIView): 8 | queryset = Property.objects.all() 9 | serializer_class = PropertySerializer 10 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/tag/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/restshop/api/tag/__init__.py -------------------------------------------------------------------------------- /restshop_project/restshop/api/tag/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Tag(models.Model): 5 | name = models.CharField(max_length=255) 6 | 7 | class Meta: 8 | ordering = ['name'] 9 | 10 | def __str__(self): 11 | return self.name 12 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/tag/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from restshop.api.tag.models import Tag 4 | 5 | 6 | class TagSerializer(serializers.ModelSerializer): 7 | 8 | class Meta: 9 | model = Tag 10 | 11 | def to_representation(self, instance): 12 | return instance.name 13 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/tag/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from restshop.api.tag.views import TagListView 4 | 5 | urlpatterns = [ 6 | url(r'^tags/$', TagListView.as_view(), name='tag-list'), 7 | ] 8 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/tag/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import generics 2 | 3 | from restshop.api.tag.models import Tag 4 | from restshop.api.tag.serializers import TagSerializer 5 | 6 | 7 | class TagListView(generics.ListAPIView): 8 | queryset = Tag.objects.all() 9 | serializer_class = TagSerializer 10 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/restshop/api/unit/__init__.py -------------------------------------------------------------------------------- /restshop_project/restshop/api/unit/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from restshop.api.product.models import Product 4 | from restshop.api.property.models import PropertyValue 5 | 6 | 7 | class Unit(models.Model): 8 | product = models.ForeignKey(to=Product, on_delete=models.CASCADE) 9 | value_set = models.ManyToManyField(to=PropertyValue) 10 | sku = models.CharField(max_length=255, primary_key=True) 11 | price = models.PositiveIntegerField() 12 | num_in_stock = models.PositiveIntegerField(default=5) 13 | 14 | class Meta: 15 | ordering = ['product__title', 'sku'] 16 | 17 | def __str__(self): 18 | properties = ', '.join(str(value) for value in self.value_set.all()) 19 | return '{}: {}'.format(self.product.title, properties) 20 | 21 | 22 | class UnitImage(models.Model): 23 | # Units can have same colors, but different sizes. 24 | # No need to create separate Image instances for these units. 25 | # That's why ManyToMany is used (one photo can be attached to different units). 26 | unit_set = models.ManyToManyField(to=Unit, blank=True) 27 | image = models.ImageField(upload_to='product_images/') 28 | is_main = models.BooleanField(default=False) 29 | 30 | class Meta: 31 | ordering = ['image'] 32 | 33 | def __str__(self): 34 | unit = self.unit_set.first() 35 | if unit is not None: 36 | product_title = unit.product.title 37 | else: 38 | product_title = 'No unit assigned' 39 | 40 | return '{}: {}'.format(product_title, self.image.name) 41 | 42 | def delete(self, *args, **kwargs): 43 | self.image.delete() 44 | 45 | super(UnitImage, self).delete(*args, **kwargs) 46 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/unit/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from restshop.api.unit.models import Unit 4 | 5 | 6 | class UnitSerializer(serializers.ModelSerializer): 7 | properties = serializers.SerializerMethodField() 8 | images = serializers.SerializerMethodField() 9 | 10 | class Meta: 11 | model = Unit 12 | fields = ('sku', 'price', 'properties', 'images', 'num_in_stock') 13 | 14 | def get_properties(self, obj): 15 | return [{ 16 | 'name': property_value.property.name, 17 | 'value': property_value.value 18 | } for property_value in obj.value_set.all()] 19 | 20 | def get_images(self, obj): 21 | images = obj.unitimage_set.all() 22 | 23 | if images.exists(): 24 | return [image.image.url for image in images.all()] 25 | else: 26 | return [] 27 | 28 | 29 | class UnitForOrderDetail(serializers.ModelSerializer): 30 | title = serializers.CharField(source='product.title') 31 | properties = serializers.SerializerMethodField() 32 | image = serializers.SerializerMethodField() 33 | product_id = serializers.IntegerField(source='product.id') 34 | 35 | class Meta: 36 | model = Unit 37 | fields = ('title', 'properties', 'image', 'product_id', 'price', 'sku', 'num_in_stock') 38 | 39 | def get_properties(self, obj): 40 | return [{ 41 | 'name': property_value.property.name, 42 | 'value': property_value.value 43 | } for property_value in obj.value_set.all()] 44 | 45 | def get_image(self, obj): 46 | image = obj.unitimage_set.order_by('-is_main').first() 47 | 48 | if image is None: 49 | return None 50 | 51 | return image.image.url 52 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/unit/urls.py: -------------------------------------------------------------------------------- 1 | 2 | urlpatterns = [ 3 | 4 | ] 5 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/unit/views.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/restshop/api/unit/views.py -------------------------------------------------------------------------------- /restshop_project/restshop/api/user/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/restshop/api/user/__init__.py -------------------------------------------------------------------------------- /restshop_project/restshop/api/user/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.db import models 3 | 4 | 5 | class Seller(models.Model): 6 | user = models.OneToOneField(to=User, on_delete=models.CASCADE) 7 | name = models.CharField(max_length=255) 8 | address = models.CharField(max_length=255) 9 | 10 | def __str__(self): 11 | return self.name 12 | 13 | 14 | class DeliveryInfo(models.Model): 15 | user = models.OneToOneField(to=User, on_delete=models.CASCADE) 16 | name = models.CharField(max_length=255) 17 | address = models.CharField(max_length=255) 18 | phone = models.CharField(max_length=31) 19 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/user/serializers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from rest_framework import serializers 3 | from rest_framework.validators import UniqueValidator 4 | 5 | from restshop.api.user.models import DeliveryInfo 6 | 7 | 8 | class UserSerializer(serializers.ModelSerializer): 9 | 10 | class Meta: 11 | model = User 12 | fields = ('email', 'password') 13 | extra_kwargs = { 14 | 'password': { 15 | 'write_only': True, 16 | 'min_length': 6, 17 | 'max_length': 127 18 | }, 19 | 'email': { 20 | 'validators': [UniqueValidator(queryset=User.objects.all())] 21 | } 22 | } 23 | 24 | 25 | class SellerSerializer(serializers.ModelSerializer): 26 | name = serializers.CharField(source='seller.name') 27 | address = serializers.CharField(source='seller.address') 28 | 29 | class Meta: 30 | model = User 31 | fields = ('email', 'password', 'name', 'address') 32 | 33 | 34 | class DeliveryInfoSerializer(serializers.ModelSerializer): 35 | 36 | class Meta: 37 | model = DeliveryInfo 38 | fields = ('name', 'address', 'phone') 39 | 40 | 41 | class PasswordSerializer(serializers.Serializer): 42 | password = serializers.CharField(min_length=6, max_length=127) 43 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/user/service.py: -------------------------------------------------------------------------------- 1 | from restshop.api.user.models import DeliveryInfo 2 | 3 | 4 | class DeliveryInfoService: 5 | 6 | @staticmethod 7 | def delete_by_user(user): 8 | """Remove current delivery info for user, if exists. 9 | Return True if successfully deleted. 10 | """ 11 | try: 12 | user.deliveryinfo.delete() 13 | return True 14 | except DeliveryInfo.DoesNotExist: 15 | return False 16 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/user/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from rest_framework_sav.views import session_auth_view 3 | 4 | from restshop.api.user.views import UserView, UserCreateView, SellerCreateView, ChangePasswordView, DeliveryInfoView 5 | 6 | urlpatterns = [ 7 | url(r'^user/$', UserView.as_view(), name='user'), 8 | url(r'^user/create/$', UserCreateView.as_view(), name='user-create'), 9 | url(r'^seller/create/$', SellerCreateView.as_view(), name='seller-create'), 10 | url(r'^deliveryinfo/$', DeliveryInfoView.as_view(), name='delivery-info'), 11 | url(r'^auth/$', session_auth_view, name='auth'), 12 | url(r'^password/$', ChangePasswordView.as_view(), name='change-password'), 13 | ] 14 | -------------------------------------------------------------------------------- /restshop_project/restshop/api/user/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User, Group, Permission 2 | from django.core.exceptions import ObjectDoesNotExist 3 | from django.contrib.auth import update_session_auth_hash 4 | from rest_framework import generics, status 5 | from rest_framework.permissions import AllowAny, IsAuthenticated 6 | from rest_framework.response import Response 7 | from rest_framework.views import APIView 8 | 9 | from restshop.api.user.models import DeliveryInfo, Seller 10 | from restshop.api.user.serializers import UserSerializer, SellerSerializer, DeliveryInfoSerializer, PasswordSerializer 11 | from restshop.api.user.service import DeliveryInfoService 12 | 13 | 14 | class UserCreateView(APIView): 15 | permission_classes = (AllowAny,) 16 | 17 | def post(self, request): 18 | serializer = UserSerializer(data=request.data) 19 | serializer.is_valid(raise_exception=True) 20 | 21 | user = User.objects.create( 22 | email=serializer.validated_data['email'], 23 | username=serializer.validated_data['email'] 24 | ) 25 | 26 | user.set_password(serializer.validated_data['password']) 27 | user.save() 28 | 29 | return Response(status=status.HTTP_201_CREATED) 30 | 31 | 32 | class UserView(APIView): 33 | permission_classes = (AllowAny,) 34 | 35 | def get(self, request): 36 | if bool(request.user.is_anonymous): 37 | return Response() 38 | 39 | return Response(UserSerializer(request.user).data) 40 | 41 | 42 | class SellerCreateView(APIView): 43 | permission_classes = (AllowAny,) 44 | 45 | def post(self, request): 46 | serializer = SellerSerializer(data=request.data) 47 | serializer.is_valid(raise_exception=True) 48 | 49 | user = User.objects.create( 50 | email=serializer.validated_data['email'], 51 | username=serializer.validated_data['email'] 52 | ) 53 | 54 | staff_group = self.get_staff_group() 55 | user.groups.add(staff_group) 56 | 57 | user.set_password(serializer.validated_data['password']) 58 | user.save() 59 | 60 | Seller.objects.create( 61 | user=user, 62 | name=serializer.validated_data['seller']['name'], 63 | address=serializer.validated_data['seller']['address'] 64 | ) 65 | 66 | return Response(UserSerializer(user).data) 67 | 68 | def get_staff_group(self): 69 | """Get staff group with seller permissions or create if does not exist.""" 70 | try: 71 | return Group.objects.get(name='Staff') 72 | except ObjectDoesNotExist: 73 | group = Group.objects.create(name='Staff') 74 | 75 | all_permissions = ('add', 'change', 'delete') 76 | content_types = { 77 | 'unit': all_permissions, 78 | 'product': all_permissions, 79 | 'unitimage': all_permissions, 80 | 'orderunit': all_permissions, 81 | 'order': ('change',), 82 | 'property': ('add', 'change'), 83 | 'propertyvalue': ('add', 'change') 84 | } 85 | 86 | for content_type in content_types: 87 | for permission in content_types[content_type]: 88 | codename = '{}_{}'.format(permission, content_type) 89 | permission = Permission.objects.get(codename=codename) 90 | group.permissions.add(permission) 91 | 92 | return group 93 | 94 | 95 | class ChangePasswordView(APIView): 96 | permission_classes = (IsAuthenticated,) 97 | 98 | def post(self, request): 99 | serializer = PasswordSerializer(data=request.data) 100 | serializer.is_valid(raise_exception=True) 101 | 102 | user = request.user 103 | user.set_password(serializer.data['password']) 104 | user.save() 105 | 106 | # Need to relogin user, because he is logged out after password change. 107 | update_session_auth_hash(request, user) 108 | 109 | return Response(status=status.HTTP_200_OK) 110 | 111 | 112 | class DeliveryInfoView(APIView): 113 | permission_classes = (IsAuthenticated,) 114 | 115 | def get(self, request): 116 | user = request.user 117 | try: 118 | deliveryinfo = user.deliveryinfo 119 | except DeliveryInfo.DoesNotExist: 120 | return Response({}) 121 | 122 | return Response(DeliveryInfoSerializer(deliveryinfo).data) 123 | 124 | def post(self, request): 125 | serializer = DeliveryInfoSerializer(data=request.data) 126 | serializer.is_valid(raise_exception=True) 127 | 128 | DeliveryInfoService.delete_by_user(request.user) 129 | 130 | serializer.save(user=request.user) 131 | 132 | return Response(status=status.HTTP_201_CREATED) 133 | -------------------------------------------------------------------------------- /restshop_project/restshop/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class RestshopConfig(AppConfig): 5 | name = 'restshop' 6 | -------------------------------------------------------------------------------- /restshop_project/restshop/fixtures/.gitignore: -------------------------------------------------------------------------------- 1 | product_images/ 2 | *.json 3 | -------------------------------------------------------------------------------- /restshop_project/restshop/fixtures/process_raw.py: -------------------------------------------------------------------------------- 1 | from json import load, dump 2 | from argparse import ArgumentParser 3 | 4 | from restshop.fixtures.fixture_creator import FixtureCreator 5 | 6 | 7 | def get_args(): 8 | parser = ArgumentParser(description='Process raw data after scraping') 9 | 10 | parser.add_argument('-i', 11 | metavar='INPUT', 12 | dest='infile', 13 | required=True, 14 | help='JSON file with scraped data') 15 | parser.add_argument('-o', 16 | metavar='OUTPUT', 17 | dest='outfile', 18 | required=True, 19 | help='target JSON') 20 | parser.add_argument('-s', 21 | metavar='SELLER', 22 | dest='seller', 23 | help='seller name') 24 | 25 | return parser.parse_args() 26 | 27 | 28 | def main(): 29 | args = get_args() 30 | 31 | with open(args.infile) as infile: 32 | data = load(infile) 33 | 34 | fm = FixtureCreator(data, args.seller) 35 | fixtures = fm.get_fixtures() 36 | 37 | with open(args.outfile, 'w') as outfile: 38 | dump(fixtures, outfile, indent=4) 39 | 40 | 41 | if __name__ == '__main__': 42 | main() 43 | -------------------------------------------------------------------------------- /restshop_project/restshop/fixtures/products/products/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/restshop/fixtures/products/products/__init__.py -------------------------------------------------------------------------------- /restshop_project/restshop/fixtures/products/products/items.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Define here the models for your scraped items 4 | # 5 | # See documentation in: 6 | # http://doc.scrapy.org/en/latest/topics/items.html 7 | 8 | import scrapy 9 | 10 | 11 | class ProductsItem(scrapy.Item): 12 | # define the fields for your item here like: 13 | # name = scrapy.Field() 14 | pass 15 | -------------------------------------------------------------------------------- /restshop_project/restshop/fixtures/products/products/middlewares.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Define here the models for your spider middleware 4 | # 5 | # See documentation in: 6 | # http://doc.scrapy.org/en/latest/topics/spider-middleware.html 7 | 8 | from scrapy import signals 9 | 10 | 11 | class ProductsSpiderMiddleware(object): 12 | # Not all methods need to be defined. If a method is not defined, 13 | # scrapy acts as if the spider middleware does not modify the 14 | # passed objects. 15 | 16 | @classmethod 17 | def from_crawler(cls, crawler): 18 | # This method is used by Scrapy to create your spiders. 19 | s = cls() 20 | crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) 21 | return s 22 | 23 | def process_spider_input(self, response, spider): 24 | # Called for each response that goes through the spider 25 | # middleware and into the spider. 26 | 27 | # Should return None or raise an exception. 28 | return None 29 | 30 | def process_spider_output(self, response, result, spider): 31 | # Called with the results returned from the Spider, after 32 | # it has processed the response. 33 | 34 | # Must return an iterable of Request, dict or Item objects. 35 | for i in result: 36 | yield i 37 | 38 | def process_spider_exception(self, response, exception, spider): 39 | # Called when a spider or process_spider_input() method 40 | # (from other spider middleware) raises an exception. 41 | 42 | # Should return either None or an iterable of Response, dict 43 | # or Item objects. 44 | pass 45 | 46 | def process_start_requests(self, start_requests, spider): 47 | # Called with the start requests of the spider, and works 48 | # similarly to the process_spider_output() method, except 49 | # that it doesn’t have a response associated. 50 | 51 | # Must return only requests (not items). 52 | for r in start_requests: 53 | yield r 54 | 55 | def spider_opened(self, spider): 56 | spider.logger.info('Spider opened: %s' % spider.name) 57 | -------------------------------------------------------------------------------- /restshop_project/restshop/fixtures/products/products/pipelines.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Define your item pipelines here 4 | # 5 | # Don't forget to add your pipeline to the ITEM_PIPELINES setting 6 | # See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html 7 | 8 | 9 | class ProductsPipeline(object): 10 | def process_item(self, item, spider): 11 | return item 12 | -------------------------------------------------------------------------------- /restshop_project/restshop/fixtures/products/products/settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Scrapy settings for products project 4 | # 5 | # For simplicity, this file contains only settings considered important or 6 | # commonly used. You can find more settings consulting the documentation: 7 | # 8 | # http://doc.scrapy.org/en/latest/topics/settings.html 9 | # http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html 10 | # http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html 11 | 12 | BOT_NAME = 'products' 13 | 14 | SPIDER_MODULES = ['products.spiders'] 15 | NEWSPIDER_MODULE = 'products.spiders' 16 | 17 | 18 | # Crawl responsibly by identifying yourself (and your website) on the user-agent 19 | #USER_AGENT = 'products (+http://www.yourdomain.com)' 20 | 21 | # Obey robots.txt rules 22 | ROBOTSTXT_OBEY = True 23 | 24 | # Configure maximum concurrent requests performed by Scrapy (default: 16) 25 | #CONCURRENT_REQUESTS = 32 26 | 27 | # Configure a delay for requests for the same website (default: 0) 28 | # See http://scrapy.readthedocs.org/en/latest/topics/settings.html#download-delay 29 | # See also autothrottle settings and docs 30 | DOWNLOAD_DELAY = 2 31 | # The download delay setting will honor only one of: 32 | #CONCURRENT_REQUESTS_PER_DOMAIN = 16 33 | #CONCURRENT_REQUESTS_PER_IP = 16 34 | 35 | # Disable cookies (enabled by default) 36 | #COOKIES_ENABLED = False 37 | 38 | # Disable Telnet Console (enabled by default) 39 | #TELNETCONSOLE_ENABLED = False 40 | 41 | # Override the default request headers: 42 | #DEFAULT_REQUEST_HEADERS = { 43 | # 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 44 | # 'Accept-Language': 'en', 45 | #} 46 | 47 | # Enable or disable spider middlewares 48 | # See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html 49 | #SPIDER_MIDDLEWARES = { 50 | # 'products.middlewares.ProductsSpiderMiddleware': 543, 51 | #} 52 | 53 | # Enable or disable downloader middlewares 54 | # See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html 55 | #DOWNLOADER_MIDDLEWARES = { 56 | # 'products.middlewares.MyCustomDownloaderMiddleware': 543, 57 | #} 58 | 59 | # Enable or disable extensions 60 | # See http://scrapy.readthedocs.org/en/latest/topics/extensions.html 61 | #EXTENSIONS = { 62 | # 'scrapy.extensions.telnet.TelnetConsole': None, 63 | #} 64 | 65 | # Configure item pipelines 66 | # See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html 67 | #ITEM_PIPELINES = { 68 | # 'products.pipelines.ProductsPipeline': 300, 69 | #} 70 | 71 | # Enable and configure the AutoThrottle extension (disabled by default) 72 | # See http://doc.scrapy.org/en/latest/topics/autothrottle.html 73 | #AUTOTHROTTLE_ENABLED = True 74 | # The initial download delay 75 | #AUTOTHROTTLE_START_DELAY = 5 76 | # The maximum download delay to be set in case of high latencies 77 | #AUTOTHROTTLE_MAX_DELAY = 60 78 | # The average number of requests Scrapy should be sending in parallel to 79 | # each remote server 80 | #AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0 81 | # Enable showing throttling stats for every response received: 82 | #AUTOTHROTTLE_DEBUG = False 83 | 84 | # Enable and configure HTTP caching (disabled by default) 85 | # See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings 86 | #HTTPCACHE_ENABLED = True 87 | #HTTPCACHE_EXPIRATION_SECS = 0 88 | #HTTPCACHE_DIR = 'httpcache' 89 | #HTTPCACHE_IGNORE_HTTP_CODES = [] 90 | #HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage' 91 | -------------------------------------------------------------------------------- /restshop_project/restshop/fixtures/products/products/spiders/__init__.py: -------------------------------------------------------------------------------- 1 | # This package will contain the spiders of your Scrapy project 2 | # 3 | # Please refer to the documentation for information on how to create and manage 4 | # your spiders. 5 | -------------------------------------------------------------------------------- /restshop_project/restshop/fixtures/products/products/spiders/nike.py: -------------------------------------------------------------------------------- 1 | from urllib.request import urlretrieve 2 | from os import mkdir 3 | from os.path import exists, join 4 | 5 | import scrapy 6 | 7 | 8 | class NikeSpider(scrapy.Spider): 9 | name = 'nike' 10 | images_dir = 'product_images' 11 | urls = [{ 12 | 'url': 'https://store.nike.com/us/en_us/pw/mens-shoes/7puZoi3', 13 | 'gender': 'Men' 14 | }, { 15 | 'url': 'https://store.nike.com/us/en_us/pw/womens-shoes/7ptZoi3', 16 | 'gender': 'Women' 17 | }] 18 | 19 | def start_requests(self): 20 | for x in self.urls: 21 | url = x['url'] 22 | yield scrapy.Request(url, 23 | callback=self.parse, 24 | meta={'gender': x['gender']}) 25 | 26 | def parse(self, response): 27 | for a_tag in response.css('.exp-left-nav-category-list li:not(.exp-left-nav-more) a'): 28 | category = a_tag.css('::text').extract_first().split(' (')[0] 29 | 30 | yield response.follow(a_tag, 31 | callback=self.parse_category, 32 | meta={'tags': [ 33 | response.meta['gender'], 34 | category 35 | ]}) 36 | 37 | def parse_category(self, response): 38 | for item in response.css('.grid-item'): 39 | 40 | # Skip customizable items. 41 | if item.css('.customize-it'): 42 | continue 43 | 44 | multiple_color_options = item.css('.color-options li a') 45 | 46 | if multiple_color_options: 47 | for a_tag in multiple_color_options: 48 | yield response.follow(a_tag, 49 | callback=self.parse_item, 50 | meta={'tags': response.meta['tags']}) 51 | else: 52 | yield response.follow(item.css('.grid-item-image-wrapper a')[0], 53 | callback=self.parse_item, 54 | meta={'tags': response.meta['tags']}) 55 | 56 | def parse_item(self, response): 57 | content = response.css('.exp-pdp-main-pdp-content') 58 | 59 | sku = content.css('.exp-style-color::text').extract_first().split(': ')[1] 60 | title = content.css('.exp-product-title::text').extract_first() 61 | price = content.css('.exp-product-info .exp-pdp-local-price::text').re_first(r'\$(.*)') 62 | sizes = content.css('select[name=skuAndSize] option:not(.selectBox-disabled)::text').extract() 63 | color = content.css('.colorText::text').extract_first() 64 | links = content.css('.exp-pdp-alt-images-carousel img::attr(src)').extract() 65 | description = response.css('.pi-pdpmainbody p::text').extract_first() 66 | 67 | if description is None: 68 | description = '' 69 | 70 | # Replace PDP_THUMB with PDP_HERO to get 620x620 image instead of 60x60 thumbnail 71 | links = [link.replace('PDP_THUMB', 'PDP_HERO') for link in links] 72 | 73 | images = self.download_and_get(links, sku) 74 | 75 | if price: 76 | price = round(float(price)) 77 | 78 | if sizes: 79 | sizes = [size.strip() for size in sizes] 80 | 81 | yield { 82 | 'sku': sku, 83 | 'title': title, 84 | 'price': price, 85 | 'sizes': sizes, 86 | 'color': color, 87 | 'images': images, 88 | 'description': description, 89 | 'tags': response.meta['tags'], 90 | } 91 | 92 | def download_and_get(self, links, sku): 93 | """Download images and return their names""" 94 | if not exists(self.images_dir): 95 | mkdir(self.images_dir) 96 | 97 | names = [] 98 | 99 | for i, link in enumerate(links): 100 | image_name = '{}_{}.jpg'.format(sku, str(i)) 101 | path = join(self.images_dir, image_name) 102 | 103 | names.append(path) 104 | 105 | if link.startswith('//'): 106 | link = 'https:' + link 107 | 108 | urlretrieve(link, path) 109 | 110 | return names 111 | -------------------------------------------------------------------------------- /restshop_project/restshop/fixtures/products/scrapy.cfg: -------------------------------------------------------------------------------- 1 | # Automatically created by: scrapy startproject 2 | # 3 | # For more information about the [deploy] section see: 4 | # https://scrapyd.readthedocs.org/en/latest/deploy.html 5 | 6 | [settings] 7 | default = products.settings 8 | 9 | [deploy] 10 | #url = http://localhost:6800/ 11 | project = products 12 | -------------------------------------------------------------------------------- /restshop_project/restshop/middleware.py: -------------------------------------------------------------------------------- 1 | from rest_framework.response import Response 2 | 3 | 4 | class JsonApiMiddleware(object): 5 | def __init__(self, get_response): 6 | self.get_response = get_response 7 | 8 | def __call__(self, request): 9 | response = self.get_response(request) 10 | 11 | if isinstance(response, Response) and response.accepted_media_type == 'application/json': 12 | data = response.data 13 | response.data = {} 14 | 15 | if response.status_code >= 400: 16 | response.data['error'] = data 17 | elif data is None: 18 | response.data['data'] = data 19 | elif 'detail' in data and isinstance(data['detail'], str) and data['detail'].startswith('Session'): 20 | # Session authentication returns "detail" message which is unnecessary. 21 | response.data['data'] = None 22 | elif 'meta' in data: 23 | # If "meta" key is in data, then "data" key is there, too. 24 | response.data = data 25 | else: 26 | response.data['data'] = data 27 | 28 | response.data['status'] = response.status_code 29 | 30 | # Need to change private attribute `_is_render` to call render second time. 31 | response._is_rendered = False 32 | response.render() 33 | 34 | return response 35 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-26 15:31 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Product', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('title', models.CharField(max_length=255)), 22 | ], 23 | ), 24 | migrations.CreateModel( 25 | name='Property', 26 | fields=[ 27 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 28 | ('name', models.CharField(max_length=255)), 29 | ], 30 | ), 31 | migrations.CreateModel( 32 | name='PropertyValue', 33 | fields=[ 34 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 35 | ('value', models.CharField(max_length=255)), 36 | ('property', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='restshop.Property')), 37 | ], 38 | ), 39 | migrations.CreateModel( 40 | name='Tag', 41 | fields=[ 42 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 43 | ('name', models.CharField(max_length=255)), 44 | ('product_list', models.ManyToManyField(to='restshop.Product')), 45 | ], 46 | ), 47 | migrations.CreateModel( 48 | name='Unit', 49 | fields=[ 50 | ('sku', models.CharField(max_length=255, primary_key=True, serialize=False)), 51 | ('price', models.PositiveIntegerField()), 52 | ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='restshop.Product')), 53 | ('value_list', models.ManyToManyField(to='restshop.PropertyValue')), 54 | ], 55 | ), 56 | ] 57 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0002_auto_20170826_1853.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-26 15:53 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('restshop', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='unit', 17 | old_name='value_list', 18 | new_name='value_set', 19 | ), 20 | migrations.RemoveField( 21 | model_name='tag', 22 | name='product_list', 23 | ), 24 | migrations.AddField( 25 | model_name='product', 26 | name='tag_set', 27 | field=models.ManyToManyField(to='restshop.Tag'), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0003_auto_20170827_0812.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-27 08:12 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('restshop', '0002_auto_20170826_1853'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='product', 17 | options={'ordering': ['id']}, 18 | ), 19 | migrations.AlterModelOptions( 20 | name='property', 21 | options={'verbose_name_plural': 'properties'}, 22 | ), 23 | migrations.AlterModelOptions( 24 | name='propertyvalue', 25 | options={'ordering': ['property__name', 'value']}, 26 | ), 27 | migrations.AlterModelOptions( 28 | name='tag', 29 | options={'ordering': ['name']}, 30 | ), 31 | migrations.AlterField( 32 | model_name='property', 33 | name='name', 34 | field=models.CharField(max_length=255, unique=True), 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0004_auto_20170828_1204.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-28 12:04 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('restshop', '0003_auto_20170827_0812'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='ProductImage', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('image', models.ImageField(upload_to='product_images/')), 21 | ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='restshop.Product')), 22 | ], 23 | ), 24 | migrations.AlterModelOptions( 25 | name='unit', 26 | options={'ordering': ['product__title', 'sku']}, 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0005_productimage_is_main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-28 12:36 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('restshop', '0004_auto_20170828_1204'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='productimage', 17 | name='is_main', 18 | field=models.BooleanField(default=False), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0006_auto_20170828_1321.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-28 13:21 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('restshop', '0005_productimage_is_main'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='productimage', 17 | options={'ordering': ['product__title', '-is_main', 'image']}, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0007_order.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-28 14:03 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.utils.timezone 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('restshop', '0006_auto_20170828_1321'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Order', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('created_at', models.DateTimeField(default=django.utils.timezone.now)), 21 | ('name', models.CharField(max_length=255)), 22 | ('phone', models.CharField(max_length=15)), 23 | ('unit_set', models.ManyToManyField(to='restshop.Unit')), 24 | ], 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0008_auto_20170828_1404.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-28 14:04 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('restshop', '0007_order'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='order', 17 | name='created_at', 18 | field=models.DateTimeField(auto_now_add=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0009_order_address.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-28 14:05 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('restshop', '0008_auto_20170828_1404'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='order', 17 | name='address', 18 | field=models.CharField(default='', max_length=255), 19 | preserve_default=False, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0010_auto_20170829_0742.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-29 07:42 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ('restshop', '0009_order_address'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Seller', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('name', models.CharField(max_length=255)), 23 | ('address', models.CharField(max_length=255)), 24 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 25 | ], 26 | ), 27 | migrations.AlterModelOptions( 28 | name='order', 29 | options={'ordering': ['-created_at']}, 30 | ), 31 | migrations.AddField( 32 | model_name='order', 33 | name='status', 34 | field=models.CharField(choices=[('PE', 'Pending'), ('RE', 'Rejected'), ('CO', 'Completed')], default='PE', max_length=2), 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0011_product_seller.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-29 09:07 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('restshop', '0010_auto_20170829_0742'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='product', 18 | name='seller', 19 | field=models.ForeignKey(default=5, on_delete=django.db.models.deletion.CASCADE, to='restshop.Seller'), 20 | preserve_default=False, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0012_auto_20170829_0950.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-29 09:50 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('restshop', '0011_product_seller'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='UnitImage', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('image', models.ImageField(upload_to='product_images/')), 20 | ('is_main', models.BooleanField(default=False)), 21 | ], 22 | options={ 23 | 'ordering': ['unit__product__title', '-is_main', 'image'], 24 | }, 25 | ), 26 | migrations.RemoveField( 27 | model_name='productimage', 28 | name='product', 29 | ), 30 | migrations.AddField( 31 | model_name='unit', 32 | name='in_stock', 33 | field=models.BooleanField(default=True), 34 | ), 35 | migrations.DeleteModel( 36 | name='ProductImage', 37 | ), 38 | migrations.AddField( 39 | model_name='unitimage', 40 | name='unit', 41 | field=models.ManyToManyField(to='restshop.Unit'), 42 | ), 43 | ] 44 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0013_auto_20170829_1012.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-29 10:12 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('restshop', '0012_auto_20170829_0950'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='product', 17 | options={'ordering': ['title']}, 18 | ), 19 | migrations.AlterModelOptions( 20 | name='unitimage', 21 | options={'ordering': ['image']}, 22 | ), 23 | migrations.RenameField( 24 | model_name='unitimage', 25 | old_name='unit', 26 | new_name='unit_set', 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0014_auto_20170829_1058.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-29 10:58 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('restshop', '0013_auto_20170829_1012'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='order', 17 | name='phone', 18 | field=models.CharField(max_length=31), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0015_auto_20170830_0720.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-30 07:20 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('restshop', '0014_auto_20170829_1058'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='OrderUnit', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('quantity', models.PositiveIntegerField()), 21 | ], 22 | ), 23 | migrations.AddField( 24 | model_name='orderunit', 25 | name='order', 26 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='restshop.Order'), 27 | ), 28 | migrations.AddField( 29 | model_name='orderunit', 30 | name='unit', 31 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='restshop.Unit'), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0016_auto_20170830_0724.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-30 07:24 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('restshop', '0015_auto_20170830_0720'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='order', 17 | name='unit_set1', 18 | field=models.ManyToManyField(related_name='orders', through='restshop.OrderUnit', to='restshop.Unit'), 19 | ), 20 | migrations.AlterField( 21 | model_name='order', 22 | name='unit_set', 23 | field=models.ManyToManyField(to='restshop.Unit'), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0017_remove_order_unit_set.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-30 07:25 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('restshop', '0016_auto_20170830_0724'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='order', 17 | name='unit_set', 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0018_auto_20170830_0725.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-30 07:25 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('restshop', '0017_remove_order_unit_set'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='order', 17 | name='unit_set1', 18 | ), 19 | migrations.AddField( 20 | model_name='order', 21 | name='unit_set', 22 | field=models.ManyToManyField(through='restshop.OrderUnit', to='restshop.Unit'), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0019_order_user.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-30 08:14 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ('restshop', '0018_auto_20170830_0725'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name='order', 20 | name='user', 21 | field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), 22 | preserve_default=False, 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0020_auto_20170904_0608.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-04 06:08 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('restshop', '0019_order_user'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='unit', 17 | name='in_stock', 18 | ), 19 | migrations.AddField( 20 | model_name='unit', 21 | name='num_in_stock', 22 | field=models.PositiveIntegerField(default=5), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0021_auto_20170904_0830.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-04 08:30 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('restshop', '0020_auto_20170904_0608'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='unitimage', 17 | name='image', 18 | field=models.ImageField(upload_to='ui/app/product_images/'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0022_auto_20170905_0554.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-05 05:54 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('restshop', '0021_auto_20170904_0830'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='unitimage', 17 | name='image', 18 | field=models.ImageField(upload_to='media/product_images/'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0023_auto_20170905_0755.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-05 07:55 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('restshop', '0022_auto_20170905_0554'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='unitimage', 17 | name='image', 18 | field=models.ImageField(upload_to='product_images/'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0024_auto_20170905_1512.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-05 15:12 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('restshop', '0023_auto_20170905_0755'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='unitimage', 17 | name='unit_set', 18 | field=models.ManyToManyField(blank=True, to='restshop.Unit'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0025_product_description.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-10-06 07:16 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('restshop', '0024_auto_20170905_1512'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='product', 17 | name='description', 18 | field=models.TextField(default=''), 19 | preserve_default=False, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0026_auto_20171017_1134.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-10-17 11:34 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ('restshop', '0025_product_description'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='CartUnit', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('quantity', models.PositiveIntegerField()), 23 | ('unit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='restshop.Unit')), 24 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 25 | ], 26 | ), 27 | migrations.AddField( 28 | model_name='orderunit', 29 | name='unit_price', 30 | field=models.PositiveIntegerField(default=0), 31 | preserve_default=False, 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0027_auto_20171017_1305.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-10-17 13:05 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('sessions', '0001_initial'), 14 | ('restshop', '0026_auto_20171017_1134'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name='cartunit', 20 | name='session', 21 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='sessions.Session'), 22 | ), 23 | migrations.AlterField( 24 | model_name='cartunit', 25 | name='user', 26 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), 27 | ), 28 | migrations.AlterField( 29 | model_name='order', 30 | name='user', 31 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0028_auto_20171018_1022.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-10-18 10:22 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('restshop', '0027_auto_20171017_1305'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='order', 17 | name='status', 18 | ), 19 | migrations.AddField( 20 | model_name='orderunit', 21 | name='status', 22 | field=models.CharField(choices=[('PE', 'Pending'), ('RE', 'Rejected'), ('CO', 'Completed')], default='PE', max_length=2), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/0029_auto_20171123_1218.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-11-23 12:18 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ('restshop', '0028_auto_20171018_1022'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='DeliveryInfo', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('name', models.CharField(max_length=255)), 23 | ('address', models.CharField(max_length=255)), 24 | ('phone', models.CharField(max_length=31)), 25 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 26 | ], 27 | ), 28 | migrations.AlterField( 29 | model_name='cartunit', 30 | name='session', 31 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cart_units', to='sessions.Session'), 32 | ), 33 | migrations.AlterField( 34 | model_name='cartunit', 35 | name='user', 36 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cart_units', to=settings.AUTH_USER_MODEL), 37 | ), 38 | migrations.AlterField( 39 | model_name='order', 40 | name='user', 41 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), 42 | ), 43 | ] 44 | -------------------------------------------------------------------------------- /restshop_project/restshop/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/restshop/migrations/__init__.py -------------------------------------------------------------------------------- /restshop_project/restshop/models.py: -------------------------------------------------------------------------------- 1 | from restshop.api.cart.models import CartUnit 2 | from restshop.api.order.models import Order 3 | from restshop.api.order_unit.models import OrderUnit 4 | from restshop.api.product.models import Product 5 | from restshop.api.property.models import Property, PropertyValue 6 | from restshop.api.tag.models import Tag 7 | from restshop.api.user.models import Seller, DeliveryInfo 8 | -------------------------------------------------------------------------------- /restshop_project/restshop/tests.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.urls import reverse 3 | from rest_framework import status 4 | from rest_framework.test import APITestCase 5 | 6 | from restshop.api.product.models import Product 7 | from restshop.api.unit.models import Unit 8 | from restshop.api.user.models import Seller 9 | 10 | 11 | class OrderTestCase(APITestCase): 12 | order_data = { 13 | 'name': 'Temp', 14 | 'phone': '+375555555', 15 | 'address': '5th Str 55' 16 | } 17 | 18 | def setUp(self): 19 | self.order_url = reverse('restshop:order-list') 20 | self.cart_url = reverse('restshop:cart') 21 | 22 | User.objects.create_user('temp', 'temp@gmail.com', '123123') 23 | self.client.login(username='temp', password='123123') 24 | 25 | seller_user = User.objects.create_user('nike', 'nike@gmail.com', '123123') 26 | seller = Seller.objects.create(user=seller_user, name='Nike', address='California') 27 | 28 | product = Product.objects.create(seller=seller, title='Nike Huarache') 29 | Unit.objects.create(product=product, sku='000000', price=95, num_in_stock=10) 30 | Unit.objects.create(product=product, sku='000001', price=95, num_in_stock=10) 31 | 32 | seller_user = User.objects.create_user('adidas', 'adidas@gmail.com', '123123') 33 | seller = Seller.objects.create(user=seller_user, name='Adidas', address='California') 34 | 35 | product = Product.objects.create(seller=seller, title='Adidas Yeezy Boost') 36 | Unit.objects.create(product=product, sku='100000', price=95, num_in_stock=10) 37 | Unit.objects.create(product=product, sku='100001', price=95, num_in_stock=10) 38 | Unit.objects.create(product=product, sku='100002', price=95, num_in_stock=0) 39 | 40 | def add_to_cart_and_assert(self, unit=None, expected_status=status.HTTP_201_CREATED): 41 | """Add to cart and check if response has the same status as expected.""" 42 | if unit is None: 43 | unit = {'sku': '000000', 'quantity': 2} 44 | 45 | response = self.client.post(self.cart_url, unit) 46 | self.assertEqual(response.status_code, expected_status) 47 | 48 | def assert_cart_length(self, expected_length, quantity_of=None): 49 | """Check cart length and, if passed, quantity of the item. 50 | quantity_of is a 2-item tuple where quantity_of[0] is the index of item 51 | and quantity_of[1] is the expected quantity.""" 52 | response = self.client.get(self.cart_url) 53 | self.assertEqual(len(response.data['data']), expected_length) 54 | 55 | if quantity_of is not None: 56 | self.assertEqual(response.data['data'][quantity_of[0]]['quantity'], quantity_of[1]) 57 | 58 | return response 59 | 60 | def order_and_assert(self, data=None, expected_status=status.HTTP_201_CREATED): 61 | if data is None: 62 | data = self.order_data 63 | 64 | response = self.client.post(self.order_url, data) 65 | self.assertEqual(response.status_code, expected_status) 66 | 67 | def assert_orders_length(self, expected_length): 68 | response = self.client.get(self.order_url) 69 | self.assertEqual(len(response.data['data']), expected_length) 70 | 71 | def test_add_to_cart(self): 72 | """Test adding to cart by a common user.""" 73 | self.add_to_cart_and_assert() 74 | self.assert_cart_length(1) 75 | 76 | def test_add_same_unit(self): 77 | """Test adding to cart the same unit. 78 | Cart should not change the number of elements. 79 | But quantity should be updated if passed.""" 80 | self.add_to_cart_and_assert({'sku': '000000'}) 81 | self.assert_cart_length(1, quantity_of=(0, 1)) 82 | 83 | self.add_to_cart_and_assert({'sku': '000000', 'quantity': 2}) 84 | self.assert_cart_length(1, quantity_of=(0, 2)) 85 | 86 | def test_add_nonexistent_unit(self): 87 | """Test adding a nonexistent unit to cart.""" 88 | self.add_to_cart_and_assert({'sku': '999'}, expected_status=status.HTTP_400_BAD_REQUEST) 89 | 90 | def test_add_too_many_items(self): 91 | """Test adding more items of unit than available.""" 92 | self.add_to_cart_and_assert({'sku': '000000', 'quantity': 15}, 93 | expected_status=status.HTTP_400_BAD_REQUEST) 94 | 95 | def test_add_to_cart_anonymously(self): 96 | """Test adding unit to cart not being logged in.""" 97 | self.client.logout() 98 | self.add_to_cart_and_assert() 99 | self.assert_cart_length(1) 100 | 101 | def test_order(self): 102 | """Test creating order as a common user.""" 103 | self.add_to_cart_and_assert() 104 | self.order_and_assert() 105 | self.assert_orders_length(1) 106 | 107 | def test_order_with_empty_cart(self): 108 | """Test creating order with no units in cart.""" 109 | self.order_and_assert(expected_status=status.HTTP_400_BAD_REQUEST) 110 | self.assert_orders_length(0) 111 | 112 | def test_order_anonymously(self): 113 | """Test order not being logged in.""" 114 | self.client.logout() 115 | self.add_to_cart_and_assert() 116 | self.order_and_assert() 117 | 118 | 119 | class UserTestCase(APITestCase): 120 | def setUp(self): 121 | self.url = reverse('restshop:auth') 122 | User.objects.create_user('temp', 'temp@gmail.com', '123123') 123 | 124 | def test_login(self): 125 | """Log in as a common user.""" 126 | response = self.client.post(self.url, {'username': 'temp', 'password': '123123'}) 127 | self.assertEqual(response.status_code, status.HTTP_200_OK) 128 | 129 | response = self.client.get(reverse('restshop:order-list')) 130 | self.assertEqual(response.status_code, status.HTTP_200_OK) 131 | 132 | self.client.logout() 133 | 134 | response = self.client.get(reverse('restshop:order-list')) 135 | self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) 136 | 137 | def test_logout(self): 138 | """Log out as a common user.""" 139 | response = self.client.post(self.url, {'username': 'temp', 'password': '123123'}) 140 | self.assertEqual(response.status_code, status.HTTP_200_OK) 141 | 142 | response = self.client.get(reverse('restshop:order-list')) 143 | self.assertEqual(response.status_code, status.HTTP_200_OK) 144 | 145 | self.client.delete(self.url) 146 | 147 | response = self.client.get(reverse('restshop:order-list')) 148 | self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) 149 | -------------------------------------------------------------------------------- /restshop_project/restshop/urls.py: -------------------------------------------------------------------------------- 1 | from os import listdir 2 | from os.path import join, isdir 3 | 4 | from django.conf.urls import include, url 5 | 6 | from restshop_project.settings import BASE_DIR 7 | 8 | API_DIR = 'restshop/api/' 9 | entities = [directory 10 | for directory in listdir(join(BASE_DIR, API_DIR)) 11 | if (isdir(join(BASE_DIR, API_DIR, directory)) 12 | and directory != '__pycache__')] 13 | 14 | urlpatterns = [ 15 | url(r'^', include('restshop.api.{}.urls'.format(entity))) 16 | for entity in entities 17 | ] 18 | -------------------------------------------------------------------------------- /restshop_project/restshop_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/restshop_project/__init__.py -------------------------------------------------------------------------------- /restshop_project/restshop_project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for restshop_project project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.11.4. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.11/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '2_&ra*tyycn%fm!0tbtil1(6s-cfq54z5#kqwvgc#53+rp)z8c' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'rest_framework', 41 | 'rest_framework_sav', 42 | 'corsheaders', 43 | 'restshop', 44 | ] 45 | 46 | MIDDLEWARE = [ 47 | 'corsheaders.middleware.CorsMiddleware', 48 | 'django.middleware.common.CommonMiddleware', 49 | 'django.middleware.security.SecurityMiddleware', 50 | 'django.contrib.sessions.middleware.SessionMiddleware', 51 | 'django.middleware.common.CommonMiddleware', 52 | 'django.middleware.csrf.CsrfViewMiddleware', 53 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 54 | 'django.contrib.messages.middleware.MessageMiddleware', 55 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 56 | 'restshop.middleware.JsonApiMiddleware', 57 | ] 58 | 59 | ROOT_URLCONF = 'restshop_project.urls' 60 | 61 | TEMPLATES = [ 62 | { 63 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 64 | 'DIRS': ['ui/build/'], 65 | 'APP_DIRS': True, 66 | 'OPTIONS': { 67 | 'context_processors': [ 68 | 'django.template.context_processors.debug', 69 | 'django.template.context_processors.request', 70 | 'django.contrib.auth.context_processors.auth', 71 | 'django.contrib.messages.context_processors.messages', 72 | ], 73 | }, 74 | }, 75 | ] 76 | 77 | WSGI_APPLICATION = 'restshop_project.wsgi.application' 78 | 79 | 80 | # Database 81 | # https://docs.djangoproject.com/en/1.11/ref/settings/#databases 82 | 83 | DATABASES = { 84 | 'default': { 85 | 'ENGINE': 'django.db.backends.postgresql', 86 | 'NAME': 'restshop', 87 | 'USER': os.environ['DB_USER'], 88 | 'PASSWORD': os.environ['DB_PASSWORD'], 89 | 'HOST': 'localhost', 90 | 'PORT': '', 91 | } 92 | } 93 | 94 | 95 | # Password validation 96 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators 97 | 98 | AUTH_PASSWORD_VALIDATORS = [ 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 101 | }, 102 | { 103 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 104 | }, 105 | { 106 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 107 | }, 108 | { 109 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 110 | }, 111 | ] 112 | 113 | 114 | # Internationalization 115 | # https://docs.djangoproject.com/en/1.11/topics/i18n/ 116 | 117 | LANGUAGE_CODE = 'en-us' 118 | 119 | TIME_ZONE = 'UTC' 120 | 121 | USE_I18N = True 122 | 123 | USE_L10N = True 124 | 125 | USE_TZ = True 126 | 127 | 128 | # Sessions 129 | SESSION_SAVE_EVERY_REQUEST = True 130 | 131 | 132 | # Static files (CSS, JavaScript, Images) 133 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ 134 | 135 | STATIC_ROOT = os.path.join(BASE_DIR, 'static/') 136 | STATIC_URL = '/static/' 137 | STATICFILES_DIRS = ( 138 | os.path.join(BASE_DIR, 'ui/build/'), 139 | ) 140 | 141 | 142 | # Media files 143 | 144 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media/') 145 | MEDIA_URL = '/media/' 146 | 147 | 148 | # REST Framework 149 | 150 | REST_FRAMEWORK = { 151 | # Use Django's standard `django.contrib.auth` permissions, 152 | # or allow read-only access for unauthenticated users. 153 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 154 | 'rest_framework.authentication.SessionAuthentication', 155 | ), 156 | 'DEFAULT_PERMISSION_CLASSES': ( 157 | 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly', 158 | ) 159 | } 160 | 161 | CORS_ORIGIN_WHITELIST = ( 162 | 'localhost:8000', 163 | ) 164 | -------------------------------------------------------------------------------- /restshop_project/restshop_project/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls import url, include 3 | from django.conf.urls.static import static 4 | from django.contrib import admin 5 | from django.views.generic import TemplateView 6 | 7 | urlpatterns = [ 8 | url(r'^admin/', admin.site.urls), 9 | url(r'^rest/', include('rest_framework.urls', namespace='rest_framework')), 10 | url(r'^api/', include('restshop.urls', namespace='restshop')), 11 | ] 12 | 13 | if settings.DEBUG: 14 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 15 | urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 16 | 17 | urlpatterns += [url(r'^(?P.*)$', TemplateView.as_view(template_name='index.html'))] 18 | -------------------------------------------------------------------------------- /restshop_project/restshop_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for restshop_project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "restshop_project.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /restshop_project/scrape_nike.sh: -------------------------------------------------------------------------------- 1 | rm -rf media/product_images 2 | cd restshop/fixtures/products 3 | rm -rf product_images 4 | rm -rf nike.json 5 | scrapy crawl nike -o nike.json 6 | cd .. 7 | python process_raw.py -i products/nike.json -o nike.json 8 | cd ../.. 9 | mv restshop/fixtures/products/product_images media/product_images 10 | python manage.py flush --noinput 11 | python manage.py loaddata nike 12 | -------------------------------------------------------------------------------- /restshop_project/ui/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /restshop_project/ui/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/bower_components" 3 | } -------------------------------------------------------------------------------- /restshop_project/ui/.gitignore: -------------------------------------------------------------------------------- 1 | logs/* 2 | !.gitkeep 3 | node_modules/ 4 | bower_components/ 5 | build/ 6 | tmp 7 | .DS_Store 8 | .idea 9 | -------------------------------------------------------------------------------- /restshop_project/ui/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globalstrict": true, 3 | "globals": { 4 | "angular": false 5 | } 6 | } -------------------------------------------------------------------------------- /restshop_project/ui/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | 5 | before_script: 6 | - export DISPLAY=:99.0 7 | - sh -e /etc/init.d/xvfb start 8 | - npm start > /dev/null & 9 | - npm run update-webdriver 10 | - sleep 1 # give server time to start 11 | 12 | script: 13 | - node_modules/.bin/karma start test/karma.conf.js --no-auto-watch --single-run --reporters=dots --browsers=Firefox 14 | - node_modules/.bin/protractor test/protractor-conf.js --browser=firefox 15 | -------------------------------------------------------------------------------- /restshop_project/ui/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2014 Google, Inc. http://angularjs.org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /restshop_project/ui/app/img/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/ui/app/img/.gitkeep -------------------------------------------------------------------------------- /restshop_project/ui/app/img/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/ui/app/img/empty.png -------------------------------------------------------------------------------- /restshop_project/ui/app/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/ui/app/img/favicon.png -------------------------------------------------------------------------------- /restshop_project/ui/app/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/ui/app/img/logo.png -------------------------------------------------------------------------------- /restshop_project/ui/app/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/ui/app/img/spinner.gif -------------------------------------------------------------------------------- /restshop_project/ui/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | RestShop 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 |
21 | 22 |
23 |
24 |
25 |
26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/app.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp', [ 3 | 'ui.router', 4 | 'ngAnimate', 5 | 'ui.bootstrap', 6 | 'rzModule', 7 | 'cgNotify' 8 | ]) 9 | .config(routeConfig) 10 | .run(addUserToRootScope) 11 | .run(addAuthorization); 12 | 13 | function routeConfig($stateProvider, $locationProvider, $urlRouterProvider) { 14 | $stateProvider 15 | .state('product-list', { 16 | url: '/products', 17 | templateUrl: '/static/partials/product-list.html', 18 | controller: 'ProductListController', 19 | controllerAs: 'vm' 20 | }) 21 | .state('product-details', { 22 | url: '/products/:id', 23 | templateUrl: '/static/partials/product-details.html', 24 | controller: 'ProductDetailsController', 25 | controllerAs: 'vm' 26 | }) 27 | .state('login', { 28 | url: '/login', 29 | templateUrl: '/static/partials/login.html', 30 | controller: 'LoginController', 31 | controllerAs: 'vm' 32 | }) 33 | .state('signup', { 34 | url: '/signup', 35 | templateUrl: '/static/partials/signup.html', 36 | controller: 'SignupController', 37 | controllerAs: 'vm' 38 | }) 39 | .state('cart', { 40 | url: '/cart', 41 | templateUrl: '/static/partials/cart.html', 42 | controller: 'CartController', 43 | controllerAs: 'vm' 44 | }) 45 | .state('profile', { 46 | url: '/profile', 47 | templateUrl: '/static/partials/profile.html', 48 | controller: 'ProfileController', 49 | controllerAs: 'vm', 50 | data: { 51 | isAuthenticated: true 52 | } 53 | }) 54 | .state('profile.info', { 55 | url: '/info', 56 | templateUrl: '/static/partials/profile.info.html', 57 | controller: 'ProfileInfoController', 58 | controllerAs: 'vm' 59 | }) 60 | .state('profile.delivery-info', { 61 | url: '/delivery-info', 62 | templateUrl: '/static/partials/profile.delivery-info.html', 63 | controller: 'ProfileDeliveryInfoController', 64 | controllerAs: 'vm' 65 | }) 66 | .state('profile.order-list', { 67 | url: '/orders', 68 | templateUrl: '/static/partials/profile.order-list.html', 69 | controller: 'ProfileOrderListController', 70 | controllerAs: 'vm' 71 | }) 72 | .state('profile.order-details', { 73 | url: '/orders/:id', 74 | templateUrl: '/static/partials/profile.order-details.html', 75 | controller: 'ProfileOrderDetailsController', 76 | controllerAs: 'vm' 77 | }); 78 | 79 | $locationProvider.html5Mode({ 80 | enabled: true, 81 | requireBase: false 82 | }); 83 | 84 | $urlRouterProvider.otherwise(($injector) => { 85 | let $state = $injector.get('$state'); 86 | $state.go('product-list'); 87 | }); 88 | } 89 | 90 | function addUserToRootScope($rootScope, userDataService) { 91 | $rootScope.user = null; 92 | $rootScope.isLogged = () => !!$rootScope.user; 93 | $rootScope.userPromise = userDataService.setUser(); 94 | } 95 | 96 | function addAuthorization($rootScope, $state) { 97 | $rootScope.$on('$stateChangeStart', (event, toState, toParams, fromState, fromParams) => { 98 | $rootScope.userPromise.then(() => { 99 | if (needAuthentication(toState) && !$rootScope.isLogged()) { 100 | event.preventDefault(); 101 | $state.go('login') 102 | } 103 | }); 104 | }); 105 | } 106 | 107 | function needAuthentication(state) { 108 | if (state.data && state.data.isAuthenticated) { 109 | return true; 110 | } else if (state.parent) { 111 | return needAuthentication(state.parent); 112 | } else { 113 | return false; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/controllers/cart.controller.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp') 3 | .controller('CartController', CartController); 4 | 5 | function CartController($rootScope, cartOrderDataService, userDataService, _) { 6 | let vm = this; 7 | 8 | vm.cartUnits = []; 9 | vm.changeQuantity = _.debounce(changeQuantity, 600); 10 | vm.deleteItem = deleteItem; 11 | vm.deliveryInfo = {}; 12 | vm.getTotalSum = getTotalSum; 13 | vm.loading = true; 14 | vm.onQuantityChange = onQuantityChange; 15 | vm.placeOrder = placeOrder; 16 | vm.successfullyOrdered = false; 17 | 18 | //////////// 19 | 20 | activate(); 21 | 22 | function activate() { 23 | cartOrderDataService.getCart().then((cartUnits) => { 24 | vm.cartUnits = cartUnits; 25 | vm.loading = false; 26 | 27 | if (vm.cartUnits.length && $rootScope.isLogged()) { 28 | userDataService.getDeliveryInfo().then((deliveryInfo) => { 29 | vm.deliveryInfo = deliveryInfo; 30 | }); 31 | } 32 | }); 33 | } 34 | 35 | function changeQuantity(cartUnit) { 36 | cartOrderDataService.addToCart(cartUnit.unit.sku, cartUnit.quantity).then(() => { 37 | cartUnit.actualQuantity = cartUnit.quantity; 38 | }, () => { 39 | cartUnit.quantity = cartUnit.actualQuantity; 40 | }); 41 | } 42 | 43 | function deleteItem(cartUnit) { 44 | cartOrderDataService.deleteFromCart(cartUnit.unit.sku).then((response) => { 45 | _.remove(vm.cartUnits, {unit: cartUnit.unit}); 46 | }); 47 | } 48 | 49 | function getTotalSum() { 50 | return vm.cartUnits.map(cu => cu.quantity * cu.unit.price).reduce((a, b) => a + b, 0); 51 | } 52 | 53 | function onQuantityChange(change, cartUnit) { 54 | if (!cartUnit.actualQuantity) { 55 | cartUnit.actualQuantity = cartUnit.quantity; 56 | } 57 | 58 | cartUnit.quantity += change; 59 | vm.changeQuantity(cartUnit); 60 | } 61 | 62 | function placeOrder() { 63 | cartOrderDataService.createOrder(vm.deliveryInfo.name, 64 | vm.deliveryInfo.address, 65 | vm.deliveryInfo.phone).then((response) => { 66 | vm.successfullyOrdered = true; 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/controllers/header.controller.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp') 3 | .controller('HeaderController', HeaderController); 4 | 5 | function HeaderController($location, $state, $window) { 6 | let vm = this; 7 | 8 | vm.searchProducts = searchProducts; 9 | vm.query = ''; 10 | 11 | //////////// 12 | 13 | activate(); 14 | 15 | function activate() { 16 | vm.query = $location.search().q || ''; 17 | } 18 | function searchProducts() { 19 | $window.location.href = $state.href('product-list', {}, {absolute: true}) + '?q=' + vm.query; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/controllers/login.controller.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp') 3 | .controller('LoginController', LoginController); 4 | 5 | function LoginController($state, userDataService, notifier) { 6 | let vm = this; 7 | 8 | vm.loginUser = loginUser; 9 | vm.user = {}; 10 | 11 | //////////// 12 | 13 | activate(); 14 | 15 | function activate() { 16 | 17 | } 18 | 19 | function loginUser() { 20 | userDataService.login(vm.user.email, vm.user.password).then((response) => { 21 | $state.go('product-list'); 22 | }, (response) => { 23 | notifier.error('Wrong email or password'); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/controllers/product-list.controller.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp') 3 | .controller('ProductListController', ProductListController); 4 | 5 | function ProductListController($location, productDataService) { 6 | let vm = this; 7 | 8 | vm.hasNext = false; 9 | vm.hasPrev = false; 10 | vm.loading = true; 11 | vm.page = 1; 12 | vm.pageNext = pageNext; 13 | vm.pagePrev = pagePrev; 14 | vm.properties = []; 15 | vm.products = []; 16 | vm.refreshFilter = refreshFilter; 17 | vm.slider = getDefaultSlider(); 18 | vm.tags = []; 19 | 20 | //////////// 21 | 22 | activate(); 23 | 24 | function activate() { 25 | initializeFilterValues(); 26 | getProducts(); 27 | } 28 | 29 | function addFilterParam(param, value) { 30 | $location.search(param, value); 31 | } 32 | 33 | function getDefaultSlider() { 34 | return { 35 | min: 0, 36 | max: 995, 37 | options: { 38 | floor: 0, 39 | ceil: 995, 40 | noSwitching: true, 41 | step: 5, 42 | translate: (value, sliderId, label) => { 43 | switch (label) { 44 | case 'model': 45 | return 'Min: $' + value; 46 | case 'high': 47 | return 'Max: $' + value; 48 | default: 49 | return '$' + value 50 | } 51 | }, 52 | onEnd: () => { 53 | refreshFilter(); 54 | } 55 | } 56 | }; 57 | } 58 | 59 | function getInitializedProperties() { 60 | return productDataService.getProperties().then((properties) => { 61 | let propertiesFromUrl = $location.search().properties || ''; 62 | propertiesFromUrl = propertiesFromUrl.split(','); 63 | 64 | return properties.map((property) => { 65 | property.values.map((value) => { 66 | let idsForValue = property.mapping[value.value]; 67 | let allIdsInUrl = idsForValue.filter(id => !propertiesFromUrl.includes(id.toString())).length == 0; 68 | let anyId = idsForValue.length > 0; 69 | value.selected = anyId && allIdsInUrl; 70 | 71 | return value; 72 | }); 73 | return property; 74 | }); 75 | }); 76 | } 77 | 78 | function getInitializedTags() { 79 | return productDataService.getTags().then((tags) => { 80 | let tagsFromUrl = $location.search().tags || ''; 81 | tagsFromUrl = tagsFromUrl.split(',').map((tag) => { 82 | return tag.toLowerCase() 83 | }); 84 | 85 | return tags.map((tag) => { 86 | return { 87 | name: tag, 88 | selected: tagsFromUrl.includes(tag.toLowerCase()) 89 | } 90 | }); 91 | }); 92 | } 93 | 94 | function getProducts() { 95 | vm.loading = true; 96 | 97 | // Query parameters in URL are the same as parameters for API request 98 | // that is why we can request API with this query string. 99 | let paramString = $location.url().split('?')[1] || ''; 100 | 101 | productDataService.getProducts(paramString).then((data) => { 102 | vm.hasNext = data.meta.has_next; 103 | vm.hasPrev = data.meta.has_prev; 104 | vm.page = data.meta.page; 105 | vm.products = data.data; 106 | vm.loading = false; 107 | 108 | initializeSlider(data.meta.min_price, data.meta.max_price); 109 | }); 110 | } 111 | 112 | function initializeFilterValues() { 113 | getInitializedTags().then((tags) => { 114 | vm.tags = tags; 115 | }); 116 | 117 | getInitializedProperties().then((properties) => { 118 | vm.properties = properties; 119 | }); 120 | 121 | vm.inStock = $location.search().in_stock == '1'; 122 | } 123 | 124 | function initializeSlider(floor, ceil) { 125 | let min = parseInt($location.search().price_min) || 0; 126 | if (min < floor || min > ceil) { 127 | min = floor; 128 | } 129 | 130 | let max = parseInt($location.search().price_max) || 1000; 131 | if (max < floor || max > ceil) { 132 | max = ceil; 133 | } 134 | 135 | vm.slider.min = min; 136 | vm.slider.max = max; 137 | vm.slider.options.floor = floor; 138 | vm.slider.options.ceil = ceil; 139 | } 140 | 141 | function pageNext() { 142 | addFilterParam('page', vm.page + 1); 143 | getProducts(); 144 | } 145 | 146 | function pagePrev() { 147 | addFilterParam('page', vm.page - 1); 148 | getProducts(); 149 | } 150 | 151 | function refreshFilter() { 152 | let selectedTags = vm.tags.filter(tagObj => tagObj.selected).map(tagObj => tagObj.name); 153 | 154 | let tagsParamValue = selectedTags.join(',') || null; 155 | addFilterParam('tags', tagsParamValue); 156 | 157 | let selectedProperties = []; 158 | for (let prop of vm.properties) { 159 | for (let value of prop.values) { 160 | if (value.selected) { 161 | selectedProperties.push(...prop.mapping[value.value]); 162 | } 163 | } 164 | } 165 | let propertiesParamValue = selectedProperties.join(',') || null; 166 | addFilterParam('properties', propertiesParamValue); 167 | 168 | let inStockParamValue = vm.inStock ? '1' : null; 169 | addFilterParam('in_stock', inStockParamValue); 170 | 171 | if (vm.slider.min == vm.slider.options.floor) { 172 | addFilterParam('price_min', null); 173 | } else { 174 | addFilterParam('price_min', vm.slider.min.toString()) 175 | } 176 | 177 | if (vm.slider.max == vm.slider.options.ceil) { 178 | addFilterParam('price_max', null); 179 | } else { 180 | addFilterParam('price_max', vm.slider.max.toString()) 181 | } 182 | 183 | // Reset page, because filtering can decrease num of pages 184 | // and current page number can become invalid. 185 | addFilterParam('page', null); 186 | 187 | getProducts(); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/controllers/profile.controller.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp') 3 | .controller('ProfileController', ProfileController); 4 | 5 | function ProfileController($state, userDataService) { 6 | let vm = this; 7 | 8 | vm.active = {}; 9 | vm.goToChild = goToChild; 10 | vm.logout = logout; 11 | vm.setActive = setActive; 12 | 13 | //////////// 14 | 15 | activate(); 16 | 17 | function activate() { 18 | 19 | } 20 | 21 | function goToChild(state) { 22 | $state.go('profile.' + state); 23 | } 24 | 25 | function logout() { 26 | userDataService.logout().then((response) => { 27 | $state.go('product-list') 28 | }); 29 | } 30 | 31 | function setActive(state) { 32 | vm.active = {}; 33 | vm.active[state] = true; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/controllers/profile.deliveryinfo.controller.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp') 3 | .controller('ProfileDeliveryInfoController', ProfileDeliveryInfoController); 4 | 5 | function ProfileDeliveryInfoController($scope, userDataService, notifier) { 6 | let vm = this; 7 | 8 | vm.setDeliveryInfo = setDeliveryInfo; 9 | vm.deliveryInfo = {}; 10 | 11 | //////////// 12 | 13 | activate(); 14 | 15 | function activate() { 16 | $scope.vm.setActive('deliveryInfo'); 17 | 18 | userDataService.getDeliveryInfo().then((deliveryInfo) => { 19 | vm.deliveryInfo = deliveryInfo; 20 | }); 21 | } 22 | 23 | function setDeliveryInfo() { 24 | userDataService.setDeliveryInfo(vm.deliveryInfo).then((response) => { 25 | notifier.success('Successfully changed delivery info'); 26 | }, (response) => { 27 | notifier.error('Cannot change delivery info'); 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/controllers/profile.info.controller.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp') 3 | .controller('ProfileInfoController', ProfileInfoController); 4 | 5 | function ProfileInfoController($scope, userDataService, notifier) { 6 | let vm = this; 7 | 8 | vm.changePassword = changePassword; 9 | vm.password = {}; 10 | 11 | //////////// 12 | 13 | activate(); 14 | 15 | function activate() { 16 | $scope.vm.setActive('info'); 17 | } 18 | 19 | function changePassword() { 20 | userDataService.setPassword(vm.password.new).then((response) => { 21 | vm.password = {}; 22 | notifier.success('Successfully changed password'); 23 | }, (response) => { 24 | notifier.error('Cannot change password'); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/controllers/profile.order-details.controller.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp') 3 | .controller('ProfileOrderDetailsController', ProfileOrderDetailsController); 4 | 5 | function ProfileOrderDetailsController($scope, $stateParams, cartOrderDataService) { 6 | let vm = this; 7 | 8 | vm.getTotalSum = getTotalSum; 9 | vm.loading = true; 10 | vm.order = {}; 11 | 12 | //////////// 13 | 14 | activate(); 15 | 16 | function activate() { 17 | $scope.vm.setActive(null); 18 | 19 | cartOrderDataService.getOrder($stateParams.id).then((order) => { 20 | vm.order = order; 21 | vm.loading = false; 22 | }); 23 | } 24 | 25 | function getTotalSum() { 26 | return vm.order.units.map(ou => ou.quantity * ou.unit.price).reduce((a, b) => a + b, 0); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/controllers/profile.order-list.controller.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp') 3 | .controller('ProfileOrderListController', ProfileOrderListController); 4 | 5 | function ProfileOrderListController($scope, cartOrderDataService) { 6 | let vm = this; 7 | 8 | vm.orders = []; 9 | vm.loading = true; 10 | 11 | //////////// 12 | 13 | activate(); 14 | 15 | function activate() { 16 | $scope.vm.setActive('orders'); 17 | 18 | cartOrderDataService.getOrders().then((orders) => { 19 | vm.orders = orders; 20 | vm.loading = false; 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/controllers/signup.controller.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp') 3 | .controller('SignupController', SignupController); 4 | 5 | function SignupController(userDataService, notifier) { 6 | let vm = this; 7 | 8 | vm.signup = signup; 9 | vm.successfullySigned = false; 10 | vm.user = {}; 11 | 12 | //////////// 13 | 14 | activate(); 15 | 16 | function activate() { 17 | 18 | } 19 | 20 | function signup() { 21 | userDataService.signup(vm.user.email, vm.user.password).then((response) => { 22 | vm.successfullySigned = true; 23 | }, (response) => { 24 | if (response.data.error.email) { 25 | if (response.data.error.email[0] == 'Enter a valid email address.') { 26 | notifier.error('There is no such email address') 27 | } else { 28 | notifier.error('Email is already taken'); 29 | } 30 | } else { 31 | notifier.error('Cannot create new account'); 32 | } 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/directives/cart-order-unit.directive.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp') 3 | .directive('rsCartOrderUnit', cartOrderUnit); 4 | 5 | function cartOrderUnit() { 6 | let directive = { 7 | restrict: 'E', 8 | templateUrl: '/static/partials/cart-order-unit.html', 9 | scope: { 10 | item: '=', 11 | isCart: '<', 12 | onQuantityChange: '&' 13 | }, 14 | controller: cartOrderUnitController, 15 | controllerAs: 'vm', 16 | bindToController: true 17 | }; 18 | 19 | return directive; 20 | } 21 | 22 | function cartOrderUnitController($scope) { 23 | let vm = this; 24 | 25 | vm.onQuantityClick = onQuantityClick; 26 | vm.onQuantityContainerClick = onQuantityContainerClick; 27 | vm.toggleHover = toggleHover; 28 | vm.quantityHovered = false; 29 | 30 | //////////// 31 | 32 | function onQuantityClick($event, change) { 33 | $event.preventDefault(); 34 | $event.stopPropagation(); 35 | 36 | vm.onQuantityChange({$change: change}); 37 | } 38 | 39 | function onQuantityContainerClick($event) { 40 | $event.preventDefault(); 41 | $event.stopPropagation(); 42 | } 43 | 44 | function toggleHover(type) { 45 | vm.quantityHovered = type == 'enter'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/directives/filter-options.directive.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp') 3 | .directive('rsFilterOptions', filterOptions); 4 | 5 | function filterOptions() { 6 | let directive = { 7 | restrict: 'E', 8 | templateUrl: '/static/partials/filter-options.html', 9 | scope: { 10 | tags: '=', 11 | inStock: '=', 12 | slider: '=', 13 | properties: '=', 14 | filterChange: '&' 15 | }, 16 | controller: filterOptionsController, 17 | controllerAs: 'vm', 18 | bindToController: true 19 | }; 20 | 21 | return directive; 22 | } 23 | 24 | /* @ngInject */ 25 | function filterOptionsController($scope, $timeout) { 26 | let vm = this; 27 | 28 | vm.filterChangeWrapper = filterChangeWrapper; 29 | vm.showSlider = showSlider; 30 | 31 | //////////// 32 | 33 | function filterChangeWrapper() { 34 | // Wrap filterChange function to wait for $digest cycle to complete. 35 | $timeout(vm.filterChange, 0, false); 36 | } 37 | 38 | function showSlider() { 39 | $timeout(() => { $scope.$broadcast('rzSliderForceRender');}); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/directives/product-list-item.directive.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp') 3 | .directive('rsProductListItem', productListItem); 4 | 5 | function productListItem() { 6 | let directive = { 7 | restrict: 'E', 8 | templateUrl: '/static/partials/product-list-item.html', 9 | scope: { 10 | product: '=' 11 | }, 12 | controller: productListItemController, 13 | controllerAs: 'vm', 14 | bindToController: true 15 | }; 16 | 17 | return directive; 18 | } 19 | 20 | /* @ngInject */ 21 | function productListItemController($state, $window) { 22 | let vm = this; 23 | 24 | vm.applyTag = applyTag; 25 | 26 | //////////// 27 | 28 | function applyTag($event, tagName) { 29 | $event.preventDefault(); 30 | $event.stopPropagation(); 31 | $window.location.href = $state.href('product-list', {}, {absolute: true}) + '?tags=' + encodeURIComponent(tagName); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/directives/pwd-check.directive.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp') 3 | .directive('pwdCheck', pwdCheck); 4 | 5 | function pwdCheck() { 6 | let directive = { 7 | require: 'ngModel', 8 | scope: { 9 | password: '=pwdCheck' 10 | }, 11 | link: (scope, elem, attrs, ngModel) => { 12 | scope.$watch('password', () => ngModel.$validate()); 13 | 14 | ngModel.$validators.pwMatch = (passwordConfirmation) => { 15 | return passwordConfirmation == scope.password; 16 | } 17 | } 18 | }; 19 | 20 | return directive; 21 | } 22 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/services/api.service.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp') 3 | .factory('apiService', apiService); 4 | 5 | function apiService($http, config) { 6 | let service = { 7 | delete: delete_, 8 | get: get, 9 | post: post 10 | }; 11 | 12 | return service; 13 | 14 | //////////// 15 | 16 | function delete_(url, id) { 17 | return $http.delete(getFullUrl(url, id)); 18 | } 19 | 20 | function get(url, id, paramString) { 21 | return $http.get(getFullUrl(url, id, paramString)); 22 | } 23 | 24 | function getFullUrl(url, id, paramString) { 25 | return config.apiUrl + '/' + url + '/' + (!!id ? id + '/' : '') + (!!paramString ? '?' + paramString : ''); 26 | } 27 | 28 | function post(url, data, id) { 29 | return $http.post(getFullUrl(url, id), data); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/services/config.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp') 3 | .constant('config', { 4 | apiUrl: 'http://localhost:8000/api', 5 | emptyImageUrl: '/static/img/empty.png' 6 | }) 7 | .constant('_', window._) 8 | .config(configCsrf); 9 | 10 | function configCsrf($httpProvider) { 11 | $httpProvider.defaults.xsrfCookieName = 'csrftoken'; 12 | $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken'; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/services/data-services/cart-order-data.service.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp') 3 | .factory('cartOrderDataService', cartOrderDataService); 4 | 5 | function cartOrderDataService(apiService) { 6 | let service = { 7 | addToCart: addToCart, 8 | deleteFromCart: deleteFromCart, 9 | getCart: getCart, 10 | getOrder: getOrder, 11 | getOrders: getOrders, 12 | createOrder: createOrder 13 | }; 14 | 15 | return service; 16 | 17 | //////////// 18 | 19 | function addToCart(sku, quantity) { 20 | return apiService.post('cart', {sku: sku, quantity: quantity}); 21 | } 22 | 23 | function deleteFromCart(sku) { 24 | return apiService.delete('cart', sku); 25 | } 26 | 27 | function getCart() { 28 | return apiService.get('cart').then((response) => { 29 | return response.data.data; 30 | }); 31 | } 32 | 33 | function getOrder(id) { 34 | return apiService.get('orders', id).then((response) => { 35 | return response.data.data; 36 | }); 37 | } 38 | 39 | function getOrders() { 40 | return apiService.get('orders').then((response) => { 41 | return response.data.data; 42 | }); 43 | } 44 | 45 | function createOrder(name, address, phone) { 46 | return apiService.post('orders', { 47 | name: name, 48 | address: address, 49 | phone: phone 50 | }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/services/data-services/product-data.service.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp') 3 | .factory('productDataService', productDataService); 4 | 5 | function productDataService(apiService, propertiesTagService, config) { 6 | let service = { 7 | getProduct: getProduct, 8 | getProducts: getProducts, 9 | getProperties: getProperties, 10 | getTags: getTags 11 | }; 12 | 13 | return service; 14 | 15 | //////////// 16 | 17 | function getProduct(id) { 18 | return apiService.get('products', id).then((response) => { 19 | for (let unit of response.data.data.units) { 20 | if (unit.images.length == 0) { 21 | unit.images.push(config.emptyImageUrl); 22 | } 23 | } 24 | 25 | return response.data.data; 26 | }); 27 | } 28 | 29 | function getProducts(paramString) { 30 | return apiService.get('products', null, paramString).then((response) => { 31 | let items = response.data.data; 32 | 33 | for (let i = 0; i < items.length; i++) { 34 | if (items[i].image == null) { 35 | items[i].image = config.emptyImageUrl; 36 | } 37 | } 38 | 39 | response.data.data = items; 40 | 41 | return response.data; 42 | }); 43 | } 44 | 45 | function getProperties() { 46 | return apiService.get('properties').then((response) => { 47 | return propertiesTagService.filterProperties(response.data.data); 48 | }); 49 | } 50 | 51 | function getTags() { 52 | return apiService.get('tags').then((response) => { 53 | return propertiesTagService.orderTags(response.data.data); 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/services/data-services/user-data.service.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp') 3 | .factory('userDataService', userDataService); 4 | 5 | function userDataService(apiService, $rootScope) { 6 | let service = { 7 | getDeliveryInfo: getDeliveryInfo, 8 | getUser: getUser, 9 | login: login, 10 | logout: logout, 11 | setDeliveryInfo: setDeliveryInfo, 12 | setPassword: setPassword, 13 | setUser: setUser, 14 | signup: signup 15 | }; 16 | 17 | return service; 18 | 19 | //////////// 20 | 21 | function getDeliveryInfo() { 22 | return apiService.get('deliveryinfo').then((response) => { 23 | return response.data.data; 24 | }); 25 | } 26 | 27 | function getUser() { 28 | return apiService.get('user').then((response) => { 29 | return response.data.data; 30 | }); 31 | } 32 | 33 | function login(username, password) { 34 | return apiService.post('auth', { 35 | username: username, 36 | password: password 37 | }).then((response) => { 38 | return setUser(); 39 | }); 40 | } 41 | 42 | function logout() { 43 | return apiService.delete('auth').then((response) => { 44 | $rootScope.user = null; 45 | }); 46 | } 47 | 48 | function setDeliveryInfo(deliveryInfo) { 49 | return apiService.post('deliveryinfo', deliveryInfo); 50 | } 51 | 52 | function setPassword(password) { 53 | return apiService.post('password', {password: password}); 54 | } 55 | 56 | function setUser() { 57 | return getUser().then((user) => { 58 | $rootScope.user = user; 59 | return user; 60 | }); 61 | } 62 | 63 | function signup(email, password) { 64 | return apiService.post('user/create', { 65 | email: email, 66 | password: password 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/services/notifier.service.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp') 3 | .factory('notifier', notifier); 4 | 5 | function notifier(notify) { 6 | let service = { 7 | error: error, 8 | success: success, 9 | warning: warning 10 | }; 11 | 12 | return service; 13 | 14 | //////////// 15 | 16 | function error(message) { 17 | notify({ 18 | message: message, 19 | classes: 'error', 20 | duration: 6000, 21 | position: 'right' 22 | }); 23 | } 24 | 25 | function success(message) { 26 | notify({ 27 | message: message, 28 | classes: 'success', 29 | duration: 4000, 30 | position: 'right' 31 | }); 32 | } 33 | 34 | function warning(message) { 35 | notify({ 36 | message: message, 37 | classes: 'warning', 38 | duration: 6500, 39 | position: 'right' 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /restshop_project/ui/app/js/services/properties-tags.service.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('restShopApp') 3 | .factory('propertiesTagService', propertiesTagService); 4 | 5 | function propertiesTagService(_) { 6 | let service = { 7 | filterProperties: filterProperties, 8 | orderProperties: orderProperties, 9 | orderTags: orderTags 10 | }; 11 | 12 | return service; 13 | 14 | //////////// 15 | 16 | function createMapping(values) { 17 | let mapping = {}; 18 | 19 | for (let value of values) { 20 | mapping[value.value] = [value.id]; 21 | } 22 | 23 | return [values.map(v => v.value), mapping]; 24 | } 25 | 26 | function filterColors(colors) { 27 | let uniqueColors = [ 28 | 'Black', 29 | 'Grey', 30 | 'White', 31 | 'Red', 32 | 'Blue', 33 | 'Green', 34 | 'Yellow', 35 | 'Pink', 36 | 'Orange' 37 | ].sort(); 38 | 39 | let mapping = {}; 40 | 41 | for (let uniqueColor of uniqueColors) { 42 | mapping[uniqueColor] = []; 43 | for (let color of colors) { 44 | if (color.value.toLowerCase().includes(uniqueColor.toLowerCase())) { 45 | mapping[uniqueColor].push(color.id); 46 | } 47 | } 48 | } 49 | 50 | return [uniqueColors, mapping]; 51 | } 52 | 53 | function filterProperties(properties) { 54 | for (let prop of properties) { 55 | let result = []; 56 | 57 | if (prop.name.toLowerCase() == 'size') { 58 | result = filterSizes(prop['values']); 59 | } else if (prop.name.toLowerCase() == 'color') { 60 | result = filterColors(prop['values']); 61 | } else { 62 | result = createMapping(prop['values']); 63 | } 64 | 65 | prop['values'] = result[0].map(v => { return {value: v} }); 66 | prop['mapping'] = result[1]; 67 | 68 | if (prop.name.toLowerCase() == 'size') { 69 | prop['values'] = _.sortBy(prop['values'], x => parseFloat(x.value) || Number.MAX_VALUE); 70 | } 71 | } 72 | 73 | return properties; 74 | } 75 | 76 | function filterSizes(sizes) { 77 | let uniqueSizes = []; 78 | 79 | function addSize(value) { 80 | if (uniqueSizes.indexOf(value) == -1 && !!value) { 81 | uniqueSizes.push(value); 82 | } 83 | } 84 | 85 | for (let size of sizes) { 86 | // '9.5' --> ['9.5'] 87 | // 'M 9.5 / W 11.5' --> ['M', '9.5', '/', 'W', '11.5'] 88 | let splittedSize = size.value.split(' '); 89 | 90 | if (splittedSize.length == 1) { 91 | addSize(splittedSize[0]); 92 | } else { 93 | addSize(splittedSize[1]); 94 | addSize(splittedSize[4]); 95 | } 96 | } 97 | 98 | let mapping = {}; 99 | 100 | for (let uniqueSize of uniqueSizes) { 101 | mapping[uniqueSize] = []; 102 | for (let size of sizes) { 103 | if (size.value.split(' ').includes(uniqueSize)) { 104 | mapping[uniqueSize].push(size.id); 105 | } 106 | } 107 | } 108 | 109 | return [uniqueSizes, mapping]; 110 | } 111 | 112 | function orderProperties(properties, valuesKey) { 113 | valuesKey = valuesKey || 'values'; 114 | 115 | for (let prop of properties) { 116 | if (prop.name.toLowerCase() == 'size') { 117 | prop[valuesKey] = orderSizes(prop[valuesKey]); 118 | } 119 | } 120 | 121 | return properties; 122 | } 123 | 124 | function orderSizes(sizes) { 125 | return _.sortBy(sizes, [ 126 | x => parseFloat(x.value) || Number.MAX_VALUE, 127 | x => parseFloat(x.value.split(' ')[1]) 128 | ]); 129 | } 130 | 131 | function orderTags(tags) { 132 | return _.sortBy(tags, [ 133 | x => x.toLowerCase() != 'men', 134 | x => x.toLowerCase() != 'women', 135 | x => x 136 | ]); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /restshop_project/ui/app/partials/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/ui/app/partials/.gitkeep -------------------------------------------------------------------------------- /restshop_project/ui/app/partials/cart-order-unit.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

Title: {{ vm.item.unit.title }}

5 |

6 | Quantity: 7 | 10 | 14 | {{ vm.item.quantity }} 15 | 19 | 20 |

21 |

Price: ${{ vm.item.unit.price }}

22 |

Total: ${{ vm.item.unit.price * vm.item.quantity }}

23 |

Status: {{ vm.item.status }}

24 |

{{ prop.name }}: {{ prop.value }}

25 |
26 |
27 | -------------------------------------------------------------------------------- /restshop_project/ui/app/partials/cart.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
5 |
6 |
8 | 10 | 13 | 14 | 15 | 17 |
18 |
19 |

Total Sum: ${{ vm.getTotalSum() }}

20 |
21 |
23 |
24 | 25 | 32 |
33 |
34 | 35 | 42 |
43 |
44 | 45 | 52 |
53 |
54 | 58 |
59 |
60 |
61 | 62 |
63 | Cart is empty 64 |
65 | 66 |
67 | 68 |
69 |
70 | 71 |
73 |
74 | Successfully ordered! 75 |
76 |
77 |
78 |
79 | -------------------------------------------------------------------------------- /restshop_project/ui/app/partials/filter-options.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

7 | Tags 9 |

10 | 11 |
12 |
13 | 17 | 18 | 21 |
22 |
23 |
24 | 25 |
26 |

29 | Filter 31 |

32 |
33 |
34 | 38 | 39 | 42 |
43 | 44 | 48 |
49 |
50 |
51 | 52 |
53 |
55 |

58 | {{ property.name }} 60 |

61 | {{ property.values.length }} options 62 |
63 |
64 | 65 | 69 | 70 | 73 |
74 |
75 |
76 |
77 |
78 | -------------------------------------------------------------------------------- /restshop_project/ui/app/partials/header.html: -------------------------------------------------------------------------------- 1 |
3 |
4 |
5 | 7 | 9 | 10 |
11 |
12 | 13 |
14 |
15 | 31 |
32 |
33 | Cart 36 |
37 |
38 | 41 | Profile 44 |
45 |
46 |
47 | -------------------------------------------------------------------------------- /restshop_project/ui/app/partials/login.html: -------------------------------------------------------------------------------- 1 | 34 | -------------------------------------------------------------------------------- /restshop_project/ui/app/partials/notifier.html: -------------------------------------------------------------------------------- 1 |
2 | {{ $message }} 3 |
4 | -------------------------------------------------------------------------------- /restshop_project/ui/app/partials/product-details.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 6 |
7 |
8 |
10 | 12 |
13 |
14 | 15 |
16 | 17 |
18 | 19 |
20 |

{{ vm.product.title }}

21 | 22 |
23 | {{ tag }} 26 |
27 | 28 |

29 | {{ vm.product.description }} 30 |

31 | 32 |
33 |
34 |

{{ optionsGroup.name }}:

35 | 36 |
43 | {{ option.value }} 48 | 49 | 51 | {{ option.value }} 52 | 53 |
54 |
55 |
56 | 57 |
{{ vm.price }}
58 | 59 | 62 |
63 |
64 |
65 | -------------------------------------------------------------------------------- /restshop_project/ui/app/partials/product-list-item.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | {{ vm.product.title }} 4 |
5 | 6 | 8 | ${{ vm.product.prices.min }} - ${{ vm.product.prices.max }} 9 | 10 | 12 | ${{ vm.product.prices.min }} 13 | 14 | 15 |
16 | {{ tag }} 19 |
20 | -------------------------------------------------------------------------------- /restshop_project/ui/app/partials/product-list.html: -------------------------------------------------------------------------------- 1 |
2 | 7 | 8 |
9 | 10 |
11 | 12 |
13 |
14 | 17 | 18 | 19 | 20 |
21 |

No matches found

22 |

Please, try another search

23 |
24 |
25 |
26 | 27 |
28 | 33 | 38 |
39 |
40 | -------------------------------------------------------------------------------- /restshop_project/ui/app/partials/profile.delivery-info.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 7 | 14 |
15 |
16 | 17 | 24 |
25 |
26 | 27 | 34 |
35 |
36 | 37 |
38 |
39 |
40 |
41 |
42 | -------------------------------------------------------------------------------- /restshop_project/ui/app/partials/profile.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 17 |
18 |
19 | 20 |
21 |
22 | -------------------------------------------------------------------------------- /restshop_project/ui/app/partials/profile.info.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 7 |
8 |

{{ user.email }}

9 |
10 |
11 |
12 | 13 | 22 |
23 |
24 | 25 | 33 |
34 |
35 | 36 |
37 |
38 |

40 | Password must be 6-127 characters 41 |

42 |

44 | Passwords don't match 45 |

46 |
47 |
48 |
49 |
50 |
51 | -------------------------------------------------------------------------------- /restshop_project/ui/app/partials/profile.order-details.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

Order #{{ vm.order.id }}

6 |
7 |
8 |
9 | 10 |
11 |

{{ vm.order.created_at | date: 'medium' }}

12 |
13 |
14 |
15 | 16 |
17 |

{{ vm.order.name }}

18 |
19 |
20 |
21 |
22 |
23 | 24 |
25 |

{{ vm.order.address }}

26 |
27 |
28 |
29 | 30 |
31 |

{{ vm.order.phone }}

32 |
33 |
34 |
35 |
36 | 37 |
38 | 41 | 42 | 43 |
44 | 45 |
46 |

Total Sum: ${{ vm.getTotalSum() }}

47 |
48 |
49 |
50 | 51 |
52 |
53 |
54 |
55 | -------------------------------------------------------------------------------- /restshop_project/ui/app/partials/profile.order-list.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 22 |
23 |
24 | -------------------------------------------------------------------------------- /restshop_project/ui/app/partials/signup.html: -------------------------------------------------------------------------------- 1 | 69 | -------------------------------------------------------------------------------- /restshop_project/ui/app/styles/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StasDeep/Rest-Shop/0600dff7710e645bbc4606f679f736d0af7a3040/restshop_project/ui/app/styles/.gitkeep -------------------------------------------------------------------------------- /restshop_project/ui/app/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | @import 'mixins'; 3 | @import 'partials/*'; 4 | -------------------------------------------------------------------------------- /restshop_project/ui/app/styles/mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin collapse-transition() { 2 | transition: height, 0.3s ease-in-out !important; 3 | } 4 | -------------------------------------------------------------------------------- /restshop_project/ui/app/styles/partials/app.scss: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | background-color: $light; 4 | } 5 | 6 | body { 7 | display: flex; 8 | flex-direction: column; 9 | overflow-y:scroll; 10 | } 11 | 12 | main { 13 | flex: 1 0 auto; 14 | } 15 | 16 | a { 17 | color: $crimson; 18 | 19 | &:focus { 20 | outline: none; 21 | } 22 | 23 | &:hover { 24 | cursor: pointer; 25 | color: darken($crimson, 8%); 26 | } 27 | } 28 | 29 | .top-buffer { 30 | margin-top: 20px; 31 | } 32 | 33 | .bottom-buffer { 34 | margin-bottom: 20px; 35 | } 36 | 37 | .btn { 38 | transition: background-color 0.4s ease; 39 | border-radius: 0; 40 | 41 | &.disabled { 42 | pointer-events: none; 43 | } 44 | 45 | &.btn-red { 46 | color: $light; 47 | background-color: $crimson; 48 | 49 | &:hover { 50 | background-color: darken($crimson, 8%); 51 | } 52 | 53 | &:hover, &:focus, &:active { 54 | outline: none; 55 | } 56 | } 57 | 58 | &.btn-large { 59 | font-size: 16pt; 60 | } 61 | 62 | &.btn-top-margin { 63 | margin-top: 10px; 64 | } 65 | 66 | &.ng-animate.ng-leave { 67 | display: none; 68 | } 69 | } 70 | 71 | .form-control { 72 | border-radius: 0; 73 | border: #cccccc solid 1px; 74 | 75 | &:focus { 76 | border-color: $crimson; 77 | } 78 | 79 | &:hover, &:active, &:focus { 80 | box-shadow: none; 81 | } 82 | } 83 | 84 | .tags-container { 85 | width: 100%; 86 | 87 | .label-tag { 88 | transition: background-color 0.4s ease, 89 | color 0.4s ease; 90 | background-color: $light; 91 | border: solid $crimson 1px; 92 | color: $crimson; 93 | border-radius: 0; 94 | text-transform: uppercase; 95 | font-weight: normal; 96 | margin-right: 3px; 97 | 98 | &:hover { 99 | background-color: $crimson; 100 | color: $light; 101 | cursor: pointer; 102 | } 103 | } 104 | } 105 | 106 | .jumbotron.jumbotron-crimson { 107 | border-radius: 0; 108 | background-color: #f9f0f2; 109 | color: $crimson; 110 | text-transform: uppercase; 111 | font-size: 16pt; 112 | text-align: center; 113 | padding: 35px; 114 | } 115 | 116 | .msg-block { 117 | padding: 10px 0; 118 | } 119 | -------------------------------------------------------------------------------- /restshop_project/ui/app/styles/partials/cart-order-unit.scss: -------------------------------------------------------------------------------- 1 | $quantity-buttons-size: 16px; 2 | 3 | rs-cart-order-unit > div { 4 | padding: 10px; 5 | border: solid #ccc 1px; 6 | margin-bottom: 8px; 7 | display: inline-block; 8 | width: 100%; 9 | color: #171717; 10 | transition: background-color 0.3s ease; 11 | overflow: hidden; 12 | 13 | &:hover { 14 | background-color: #f5f5f5; 15 | text-decoration: none; 16 | } 17 | 18 | &.no-hover:hover { 19 | background-color: $light; 20 | } 21 | 22 | .unit-image { 23 | width: 164px; 24 | display: inline-block; 25 | float: left; 26 | border: solid $image-border-color 1px; 27 | } 28 | 29 | @media (max-width: 400px) { 30 | .unit-image { 31 | width: 100%; 32 | margin-bottom: 10px; 33 | } 34 | } 35 | 36 | .unit-details { 37 | padding-left: 15px; 38 | overflow: hidden; 39 | 40 | p { 41 | margin: 3px 0; 42 | } 43 | } 44 | 45 | .quantity-button { 46 | border: solid $crimson 1px; 47 | background-color: $light; 48 | color: $crimson; 49 | width: $quantity-buttons-size; 50 | height: $quantity-buttons-size; 51 | border-radius: $quantity-buttons-size; 52 | font-weight: bold; 53 | font-size: $quantity-buttons-size; 54 | padding: 0; 55 | line-height: 14px; 56 | transition: color 0.3s ease, background-color 0.3s ease; 57 | 58 | &:hover { 59 | background-color: $crimson; 60 | color: $light; 61 | } 62 | 63 | &:focus { 64 | outline: none; 65 | } 66 | 67 | &.disabled { 68 | opacity: 0.3; 69 | pointer-events: none; 70 | background-color: $light; 71 | color: $crimson 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /restshop_project/ui/app/styles/partials/cart.scss: -------------------------------------------------------------------------------- 1 | .cart-page { 2 | .cart-unit-container { 3 | position: relative; 4 | 5 | .delete-button { 6 | position: absolute; 7 | right: 0; 8 | top: 0; 9 | border: none; 10 | background-color: transparent; 11 | color: lighten($crimson, 10%); 12 | font-size: 15pt; 13 | font-weight: bold; 14 | width: 40px; 15 | height: 40px; 16 | transition: color 0.3s ease; 17 | 18 | &:hover { 19 | color: darken($crimson, 10%); 20 | } 21 | 22 | &:focus { 23 | outline: none; 24 | } 25 | } 26 | 27 | rs-cart-order-unit > div { 28 | padding-right: 25px; 29 | } 30 | } 31 | 32 | .total-sum { 33 | margin-top: 0; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /restshop_project/ui/app/styles/partials/checkboxes.scss: -------------------------------------------------------------------------------- 1 | .checkbox { 2 | padding-left: 20px; 3 | } 4 | 5 | .checkbox label { 6 | display: inline-block; 7 | position: relative; 8 | padding-left: 5px; 9 | } 10 | 11 | .checkbox label::before { 12 | content: ""; 13 | display: inline-block; 14 | position: absolute; 15 | width: 17px; 16 | height: 17px; 17 | left: 0; 18 | margin-left: -20px; 19 | border: 1px solid #cccccc; 20 | border-radius: 0; 21 | background-color: $light; 22 | transition: border 0.15s ease-in-out, 23 | color 0.15s ease-in-out, 24 | background-color 0.35s ease-in-out; 25 | } 26 | 27 | .checkbox label:hover::before { 28 | border-color: $crimson; 29 | } 30 | 31 | .checkbox label::after { 32 | display: inline-block; 33 | position: absolute; 34 | width: 16px; 35 | height: 16px; 36 | left: 0; 37 | top: 0; 38 | margin-left: -20px; 39 | padding-left: 3px; 40 | padding-top: 1px; 41 | font-size: 11px; 42 | color: #555555; 43 | } 44 | 45 | .checkbox input[type="checkbox"] { 46 | opacity: 0; 47 | } 48 | 49 | .checkbox input[type="checkbox"]:focus + label::before { 50 | outline: none; 51 | outline-offset: -2px; 52 | } 53 | 54 | .checkbox input[type="checkbox"]:checked + label::after { 55 | font-family: 'Glyphicons Halflings', serif; 56 | content: "\e013"; 57 | } 58 | 59 | .checkbox input[type="checkbox"]:disabled + label { 60 | opacity: 0.65; 61 | } 62 | 63 | .checkbox input[type="checkbox"]:disabled + label::before { 64 | background-color: #eeeeee; 65 | cursor: not-allowed; 66 | } 67 | 68 | .checkbox.checkbox-circle label::before { 69 | border-radius: 50%; 70 | } 71 | 72 | .checkbox.checkbox-inline { 73 | margin-top: 0; 74 | } 75 | 76 | .checkbox-primary input[type="checkbox"]:checked + label::before { 77 | background-color: #428bca; 78 | border-color: #428bca; 79 | } 80 | 81 | .checkbox-primary input[type="checkbox"]:checked + label::after { 82 | color: $light; 83 | } 84 | 85 | .checkbox-danger input[type="checkbox"]:checked + label::before { 86 | background-color: $crimson; 87 | border-color: $crimson; 88 | } 89 | 90 | .checkbox-danger input[type="checkbox"]:checked + label::after { 91 | color: $light; 92 | } 93 | 94 | .checkbox-info input[type="checkbox"]:checked + label::before { 95 | background-color: #5bc0de; 96 | border-color: #5bc0de; 97 | } 98 | 99 | .checkbox-info input[type="checkbox"]:checked + label::after { 100 | color: $light; 101 | } 102 | 103 | .checkbox-warning input[type="checkbox"]:checked + label::before { 104 | background-color: #f0ad4e; 105 | border-color: #f0ad4e; 106 | } 107 | 108 | .checkbox-warning input[type="checkbox"]:checked + label::after { 109 | color: $light; 110 | } 111 | 112 | .checkbox-success input[type="checkbox"]:checked + label::before { 113 | background-color: #5cb85c; 114 | border-color: #5cb85c; 115 | } 116 | 117 | .checkbox-success input[type="checkbox"]:checked + label::after { 118 | color: $light; 119 | } 120 | 121 | .radio { 122 | padding-left: 20px; 123 | } 124 | 125 | .radio label { 126 | display: inline-block; 127 | position: relative; 128 | padding-left: 5px; 129 | } 130 | 131 | .radio label::before { 132 | content: ""; 133 | display: inline-block; 134 | position: absolute; 135 | width: 17px; 136 | height: 17px; 137 | left: 0; 138 | margin-left: -20px; 139 | border: 1px solid #cccccc; 140 | border-radius: 50%; 141 | background-color: $light; 142 | transition: border 0.15s ease-in-out; 143 | } 144 | 145 | .radio label::after { 146 | display: inline-block; 147 | position: absolute; 148 | content: " "; 149 | width: 11px; 150 | height: 11px; 151 | left: 3px; 152 | top: 3px; 153 | margin-left: -20px; 154 | border-radius: 50%; 155 | background-color: #555555; 156 | -webkit-transform: scale(0, 0); 157 | -ms-transform: scale(0, 0); 158 | -o-transform: scale(0, 0); 159 | transform: scale(0, 0); 160 | transition: transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33); 161 | } 162 | 163 | .radio input[type="radio"] { 164 | opacity: 0; 165 | } 166 | 167 | .radio input[type="radio"]:focus + label::before { 168 | outline: thin dotted; 169 | outline: 5px auto -webkit-focus-ring-color; 170 | outline-offset: -2px; 171 | } 172 | 173 | .radio input[type="radio"]:checked + label::after { 174 | transform: scale(1, 1); 175 | } 176 | 177 | .radio input[type="radio"]:disabled + label { 178 | opacity: 0.65; 179 | } 180 | 181 | .radio input[type="radio"]:disabled + label::before { 182 | cursor: not-allowed; 183 | } 184 | 185 | .radio.radio-inline { 186 | margin-top: 0; 187 | } 188 | 189 | .radio-primary input[type="radio"] + label::after { 190 | background-color: #428bca; 191 | } 192 | 193 | .radio-primary input[type="radio"]:checked + label::before { 194 | border-color: #428bca; 195 | } 196 | 197 | .radio-primary input[type="radio"]:checked + label::after { 198 | background-color: #428bca; 199 | } 200 | 201 | .radio-danger input[type="radio"] + label::after { 202 | background-color: $crimson; 203 | } 204 | 205 | .radio-danger input[type="radio"]:checked + label::before { 206 | border-color: $crimson; 207 | } 208 | 209 | .radio-danger input[type="radio"]:checked + label::after { 210 | background-color: $crimson; 211 | } 212 | 213 | .radio-info input[type="radio"] + label::after { 214 | background-color: #5bc0de; 215 | } 216 | 217 | .radio-info input[type="radio"]:checked + label::before { 218 | border-color: #5bc0de; 219 | } 220 | 221 | .radio-info input[type="radio"]:checked + label::after { 222 | background-color: #5bc0de; 223 | } 224 | 225 | .radio-warning input[type="radio"] + label::after { 226 | background-color: #f0ad4e; 227 | } 228 | 229 | .radio-warning input[type="radio"]:checked + label::before { 230 | border-color: #f0ad4e; 231 | } 232 | 233 | .radio-warning input[type="radio"]:checked + label::after { 234 | background-color: #f0ad4e; 235 | } 236 | 237 | .radio-success input[type="radio"] + label::after { 238 | background-color: #5cb85c; 239 | } 240 | 241 | .radio-success input[type="radio"]:checked + label::before { 242 | border-color: #5cb85c; 243 | } 244 | 245 | .radio-success input[type="radio"]:checked + label::after { 246 | background-color: #5cb85c; 247 | } 248 | -------------------------------------------------------------------------------- /restshop_project/ui/app/styles/partials/filter-options.scss: -------------------------------------------------------------------------------- 1 | $clear-button-color: darken($light, 10%); 2 | $input-color: darken($light, 6%); 3 | $filter-text-color: #7a7a7a; 4 | 5 | .collapse-filter-button { 6 | font-size: 14pt; 7 | 8 | .collapse-chevron { 9 | font-size: 11pt; 10 | } 11 | } 12 | 13 | .collapse-chevron { 14 | transition: transform 0.3s ease-in-out; 15 | 16 | &.flipped { 17 | transform: rotate(-180deg); 18 | } 19 | } 20 | 21 | rs-filter-options { 22 | @include collapse-transition(); 23 | 24 | .checkbox, .filter-text { 25 | color: $filter-text-color; 26 | } 27 | 28 | .filter-title { 29 | font-size: 14pt; 30 | } 31 | 32 | .filter-text { 33 | margin: 10px 0; 34 | 35 | input { 36 | width: 50px; 37 | border: none; 38 | background-color: $input-color; 39 | 40 | &:focus { 41 | outline: none; 42 | } 43 | } 44 | 45 | .filter-input-title { 46 | display: inline-block; 47 | text-transform: uppercase; 48 | width: 40px; 49 | } 50 | 51 | .clear-button { 52 | color: $clear-button-color; 53 | cursor: pointer; 54 | 55 | &:hover { 56 | color: darken($clear-button-color, 15%); 57 | } 58 | } 59 | } 60 | 61 | .filter-property-block { 62 | .property-name { 63 | margin: 15px 0 0; 64 | display: inline-block; 65 | cursor: pointer; 66 | transition: color 0.3s ease-in-out; 67 | 68 | &:hover { 69 | color: $crimson; 70 | } 71 | 72 | .collapse-chevron { 73 | font-size: 10pt; 74 | } 75 | } 76 | 77 | .num-of-options { 78 | margin-left: 5px; 79 | font-size: 8pt; 80 | color: $filter-text-color; 81 | } 82 | 83 | .property-collapsing-block { 84 | @include collapse-transition(); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /restshop_project/ui/app/styles/partials/header.scss: -------------------------------------------------------------------------------- 1 | header { 2 | margin-top: 25px; 3 | 4 | .restshop-logo { 5 | cursor: pointer; 6 | 7 | @media screen and (min-width: 645px) { 8 | & { 9 | width: 600px; 10 | } 11 | } 12 | } 13 | 14 | .search-column { 15 | display: inline-block; 16 | width: 58%; 17 | 18 | @media screen and (min-width: 645px) { 19 | & { 20 | width: 425px; 21 | } 22 | } 23 | } 24 | 25 | .extra-button-column { 26 | display: inline-block; 27 | vertical-align: top; 28 | width: 22%; 29 | 30 | @media screen and (min-width: 645px) { 31 | & { 32 | width: 95px; 33 | } 34 | } 35 | 36 | .btn-sign-in, .btn-user { 37 | width: 100%; 38 | } 39 | } 40 | 41 | .cart-button-column { 42 | display: inline-block; 43 | vertical-align: top; 44 | width: 14%; 45 | 46 | @media screen and (min-width: 645px) { 47 | & { 48 | width: 70px; 49 | } 50 | } 51 | 52 | .btn { 53 | width: 100%; 54 | } 55 | } 56 | } 57 | 58 | 59 | -------------------------------------------------------------------------------- /restshop_project/ui/app/styles/partials/login.scss: -------------------------------------------------------------------------------- 1 | .login-page { 2 | .signup-prompt { 3 | display: inline-block; 4 | margin: 0 0 0 7px; 5 | vertical-align: bottom; 6 | 7 | small { 8 | line-height: 30px; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /restshop_project/ui/app/styles/partials/notifier.scss: -------------------------------------------------------------------------------- 1 | .cg-notify-message { 2 | text-transform: uppercase; 3 | border-radius: 0; 4 | max-width: 300px; 5 | } 6 | 7 | .cg-notify-message.error { 8 | border-width: 1px; 9 | border-color: $crimson; 10 | background-color: $light; 11 | color: $crimson; 12 | box-shadow: 0 3px 15px rgba(0,0,0,0.1); 13 | } 14 | 15 | .cg-notify-message.success { 16 | border-width: 0; 17 | background-color: $crimson; 18 | color: $light; 19 | box-shadow: 0 3px 15px rgba(0,0,0,0.275); 20 | } 21 | 22 | .cg-notify-message.warning { 23 | border-width: 0; 24 | background-color: #ec7560; 25 | color: $light; 26 | box-shadow: 0 3px 15px rgba(0,0,0,0.275); 27 | } 28 | -------------------------------------------------------------------------------- /restshop_project/ui/app/styles/partials/price-slider.scss: -------------------------------------------------------------------------------- 1 | .price-slider.rzslider { 2 | 3 | .rz-pointer { 4 | transition: background-color 0.3s ease, 5 | left 0.1s ease; 6 | background-color: $crimson; 7 | } 8 | 9 | .rz-pointer:hover { 10 | background-color: darken($crimson, 8%); 11 | } 12 | 13 | .rz-pointer:focus { 14 | outline: none; 15 | } 16 | 17 | .rz-pointer::after { 18 | background-color: $light !important; 19 | } 20 | 21 | .rz-selection { 22 | background-color: $crimson; 23 | } 24 | 25 | .rz-bar-wrapper { 26 | transition: width 0.1s ease, 27 | left 0.1s ease; 28 | } 29 | 30 | .rz-bubble { 31 | transition: left 0.1s ease; 32 | text-transform: uppercase; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /restshop_project/ui/app/styles/partials/product-details.scss: -------------------------------------------------------------------------------- 1 | $color-option-size: 68px; 2 | $thumbnail-size: 60px; 3 | 4 | .product-details-page { 5 | .title { 6 | margin-top: 0; 7 | } 8 | 9 | .thumbnails-container{ 10 | padding-right: 0px; 11 | margin-right: -10px; 12 | z-index: 5; 13 | 14 | .thumbnail-image-container { 15 | margin-bottom: 5px; 16 | 17 | img { 18 | width: $thumbnail-size; 19 | height: $thumbnail-size; 20 | border: solid $image-border-color 1px; 21 | 22 | &:hover { 23 | cursor: pointer; 24 | border-color: $crimson; 25 | } 26 | } 27 | } 28 | } 29 | 30 | .selected-image-container { 31 | img { 32 | border: solid $image-border-color 1px; 33 | width: 100%; 34 | } 35 | } 36 | 37 | .option-container { 38 | display: inline-block; 39 | margin-right: 6px; 40 | margin-bottom: 6px; 41 | border: solid $image-border-color 1px; 42 | text-align: center; 43 | 44 | &:hover { 45 | cursor: pointer; 46 | border-color: $crimson; 47 | } 48 | 49 | &.selected { 50 | border-color: $crimson; 51 | } 52 | 53 | &.not-allowed { 54 | pointer-events: none; 55 | cursor: not-allowed; 56 | opacity: 0.4; 57 | } 58 | 59 | img.color-option { 60 | width: $color-option-size; 61 | height: $color-option-size; 62 | } 63 | 64 | span.option { 65 | display: inline-block; 66 | padding: 8px 12px; 67 | font-size: 12pt; 68 | } 69 | } 70 | 71 | .tags-container { 72 | margin-bottom: 12px; 73 | 74 | .label-tag { 75 | font-size: 12pt; 76 | } 77 | } 78 | 79 | .unit-price { 80 | font-size: 32px; 81 | font-weight: bold; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /restshop_project/ui/app/styles/partials/product-list-item.scss: -------------------------------------------------------------------------------- 1 | .product-item-container { 2 | display: inline-block; 3 | 4 | rs-product-list-item { 5 | transition: border-color 0.3s ease; 6 | margin-right: -1px; 7 | margin-bottom: -1px; 8 | padding: 20px; 9 | display: inline-block; 10 | width: 285px; 11 | border: transparent solid 1px; 12 | 13 | &.ng-enter, 14 | &.ng-leave, 15 | &.ng-animate { 16 | transition: none !important; 17 | } 18 | 19 | &:hover { 20 | border-color: $crimson; 21 | cursor: pointer; 22 | } 23 | 24 | .product-image { 25 | width: 245px; 26 | height: 245px; 27 | border: solid $image-border-color 1px; 28 | margin-bottom: 5px; 29 | } 30 | 31 | .product-title { 32 | color: $crimson; 33 | display: block; 34 | padding: 3px; 35 | text-transform: uppercase; 36 | } 37 | 38 | .product-price { 39 | display: inline-block; 40 | padding: 3px; 41 | color: #4b4b4b; 42 | } 43 | 44 | .product-title-container { 45 | width: 100%; 46 | height: 21px; 47 | overflow-y: hidden; 48 | } 49 | 50 | .tags-container { 51 | height: 25px; 52 | overflow-x: hidden; 53 | } 54 | } 55 | 56 | .products-not-found { 57 | padding: 20px; 58 | color: darken($crimson, 10%); 59 | 60 | .main-msg { 61 | font-size: 20pt; 62 | font-weight: bold; 63 | margin-bottom: 0; 64 | } 65 | 66 | .secondary-msg { 67 | font-size: 10pt; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /restshop_project/ui/app/styles/partials/profile.scss: -------------------------------------------------------------------------------- 1 | .profile-page { 2 | .list-group-item { 3 | border-radius: 0; 4 | transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease; 5 | 6 | &.active { 7 | background-color: $crimson; 8 | border-color: $crimson; 9 | } 10 | } 11 | } 12 | 13 | .profile-orders-page { 14 | .order-list-item { 15 | padding: 20px; 16 | border: solid #ccc 1px; 17 | margin-bottom: 8px; 18 | display: inline-block; 19 | width: 100%; 20 | color: inherit; 21 | transition: background-color 0.3s ease; 22 | 23 | &:hover { 24 | background-color: #f5f5f5; 25 | } 26 | } 27 | } 28 | 29 | .profile-order-details-page { 30 | .control-label { 31 | margin-bottom: 0; 32 | } 33 | 34 | .form-group { 35 | margin-bottom: 5px; 36 | } 37 | 38 | h2 { 39 | margin-top: 5px; 40 | } 41 | 42 | .total-sum { 43 | margin-top: 0; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /restshop_project/ui/app/styles/variables.scss: -------------------------------------------------------------------------------- 1 | $crimson: #d92a4f; 2 | $light: #fefefe; 3 | $image-border-color: darken($light, 8%); 4 | -------------------------------------------------------------------------------- /restshop_project/ui/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restshop", 3 | "dependencies": { 4 | "angular": "^1.6.4", 5 | "angular-animate": "^1.6.4", 6 | "angular-bootstrap": "^2.5.0", 7 | "angular-ui-router": "^0.4.2", 8 | "bootstrap": "^3.3.7", 9 | "angularjs-slider": "^6.4.0", 10 | "lodash": "^4.17.4", 11 | "angular-notify": "^2.5.1" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /restshop_project/ui/gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import del from 'del'; 4 | import gulp from 'gulp'; 5 | import autoprefixer from 'gulp-autoprefixer'; 6 | import babel from 'gulp-babel'; 7 | import concat from 'gulp-concat'; 8 | import csso from 'gulp-csso'; 9 | import env from 'gulp-environment'; 10 | import iife from 'gulp-iife'; 11 | import imagemin from 'gulp-imagemin'; 12 | import ngAnnotate from 'gulp-ng-annotate'; 13 | import sass from 'gulp-sass'; 14 | import sourcemaps from 'gulp-sourcemaps'; 15 | import bulkSass from 'gulp-sass-bulk-import'; 16 | import uglify from 'gulp-uglify'; 17 | import watch from 'gulp-watch'; 18 | import merge from 'merge-stream'; 19 | 20 | 21 | // ===== Paths ===== 22 | 23 | const dirs = { 24 | src: './app/', 25 | build: './build/' 26 | }; 27 | 28 | const path = { 29 | src: { // Where to take files from. 30 | vendorJs: [ 31 | 'bower_components/lodash/dist/lodash.min.js', 32 | 'bower_components/angular/angular.min.js', 33 | 'bower_components/angular-animate/angular-animate.min.js', 34 | 'bower_components/angular-ui-router/release/angular-ui-router.min.js', 35 | 'bower_components/angular-bootstrap/ui-bootstrap.min.js', 36 | 'bower_components/angularjs-slider/dist/rzslider.min.js', 37 | 'bower_components/angular-notify/dist/angular-notify.js' 38 | ], 39 | srcJs: [ 40 | 'js/app.js', 41 | 'js/controllers/**/*.js', 42 | 'js/directives/**/*.js', 43 | 'js/filters/**/*.js', 44 | 'js/services/**/*.js' 45 | ], 46 | htmlIndex: 'index.html', 47 | htmlPartials: 'partials/**/*', 48 | vendorStyles: [ 49 | 'bower_components/bootstrap/dist/css/bootstrap.css', 50 | 'bower_components/angularjs-slider/dist/rzslider.css', 51 | 'bower_components/angular-notify/dist/angular-notify.css', 52 | ], 53 | srcStyles: [ 54 | 'styles/main.scss' 55 | ], 56 | fonts: [ 57 | 'bower_components/bootstrap/dist/fonts/*' 58 | ], 59 | img: 'img/**/*' 60 | }, 61 | build: { // Where to put files into. 62 | js: 'js/', 63 | htmlIndex: '', // root of build folder 64 | htmlPartials: 'partials/', 65 | styles: 'css/', 66 | fonts: 'fonts/', 67 | img: 'img/' 68 | }, 69 | watch: { // Which files changes to watch. 70 | html: '**/*.html', 71 | styles: 'styles/**/*.scss', 72 | js: 'js/**/*.js', 73 | img: 'img/**/*' 74 | } 75 | }; 76 | 77 | const addDirName = (fileset) => { 78 | if (fileset instanceof Array) { 79 | return fileset.map((x) => dirs.src + x); 80 | } else { 81 | return dirs.src + fileset; 82 | } 83 | }; 84 | 85 | const take = (pathKey) => { 86 | let fileset = addDirName(path.src[pathKey]); 87 | return gulp.src(fileset); 88 | }; 89 | 90 | const put = (pathKey) => { 91 | let fileset = dirs.build + path.build[pathKey]; 92 | return gulp.dest(fileset); 93 | }; 94 | 95 | 96 | // ===== Tasks ===== 97 | 98 | // HTML 99 | 100 | gulp.task('html:build', () => { 101 | take('htmlIndex') 102 | .pipe(put('htmlIndex')); 103 | 104 | return take('htmlPartials') 105 | .pipe(put('htmlPartials')); 106 | }); 107 | 108 | 109 | // Styles 110 | 111 | gulp.task('styles:build', () => { 112 | let cssStream = take('vendorStyles'); 113 | 114 | let scssStream = take('srcStyles') 115 | .pipe(env.if.not.production(sourcemaps.init())) 116 | .pipe(bulkSass()) 117 | .pipe(sass()) 118 | .pipe(autoprefixer()) 119 | .pipe(env.if.production(csso())) 120 | .pipe(env.if.not.production(sourcemaps.write())); 121 | 122 | return merge(cssStream, scssStream) 123 | .pipe(env.if.not.production(sourcemaps.init())) 124 | .pipe(concat('app.css')) 125 | .pipe(env.if.not.production(sourcemaps.write())) 126 | .pipe(put('styles')); 127 | }); 128 | 129 | 130 | // JavaScript 131 | 132 | gulp.task('js:build', () => { 133 | take('vendorJs') 134 | .pipe(env.if.not.production(sourcemaps.init())) 135 | .pipe(concat('vendor.js')) 136 | .pipe(env.if.not.production(sourcemaps.write())) 137 | .pipe(put('js')); 138 | 139 | return take('srcJs') 140 | .pipe(babel()) 141 | .pipe(ngAnnotate()) 142 | .pipe(env.if.not.production(sourcemaps.init())) 143 | .pipe(env.if.production(iife({useStrict: false}))) 144 | .pipe(concat('app.js')) 145 | .pipe(env.if.production(uglify())) 146 | .pipe(env.if.not.production(sourcemaps.write())) 147 | .pipe(put('js')); 148 | }); 149 | 150 | 151 | // Images 152 | 153 | gulp.task('img:build', () => { 154 | return take('img') 155 | .pipe(imagemin()) 156 | .pipe(put('img')); 157 | }); 158 | 159 | 160 | // Fonts 161 | 162 | gulp.task('fonts:build', () => { 163 | return take('fonts') 164 | .pipe(put('fonts')); 165 | }); 166 | 167 | 168 | // Watch 169 | 170 | const watchFiles = (pathKey) => { 171 | let fileset = addDirName(path.watch[pathKey]); 172 | let taskName = pathKey + ':build'; 173 | 174 | watch(fileset, () => { 175 | gulp.start(taskName); 176 | }); 177 | }; 178 | 179 | gulp.task('watch', () => { 180 | const keys = ['html', 'styles', 'js', 'img']; 181 | for (let i = 0; i < keys.length; i++) { 182 | watchFiles(keys[i]); 183 | } 184 | }); 185 | 186 | 187 | // Other 188 | 189 | gulp.task('clean', () => del(dirs.build)); 190 | 191 | gulp.task('build', ['html:build', 'styles:build', 'js:build', 'img:build', 'fonts:build']); 192 | 193 | gulp.task('default', env.is.development() ? ['build', 'watch'] : ['build']); 194 | -------------------------------------------------------------------------------- /restshop_project/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restshop", 3 | "description": "Training e-commerce with Python 3 and Angular", 4 | "repository": "https://github.com/StasDeep/Rest-Shop/projects/1", 5 | "devDependencies": { 6 | "babel-core": "^6.26.0", 7 | "babel-preset-es2015": "^6.24.1", 8 | "bower": "^1.3.1", 9 | "del": "^3.0.0", 10 | "gulp": "^3.9.0", 11 | "gulp-autoprefixer": "^4.0.0", 12 | "gulp-babel": "^7.0.0", 13 | "gulp-concat": "^2.6.1", 14 | "gulp-csso": "^3.0.0", 15 | "gulp-environment": "^1.5.1", 16 | "gulp-iife": "^0.3.0", 17 | "gulp-imagemin": "^3.3.0", 18 | "gulp-ng-annotate": "^2.0.0", 19 | "gulp-sass": "^3.1.0", 20 | "gulp-sass-bulk-import": "^1.0.1", 21 | "gulp-sourcemaps": "^2.6.1", 22 | "gulp-uglify": "^3.0.0", 23 | "gulp-watch": "^4.3.11", 24 | "merge-stream": "^1.0.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /restshop_project/ui/test/e2e/scenarios.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* https://github.com/angular/protractor/blob/master/docs/getting-started.md */ 4 | 5 | describe('my app', function() { 6 | 7 | browser.get('index.html'); 8 | 9 | it('should automatically redirect to /view1 when location hash/fragment is empty', function() { 10 | expect(browser.getLocationAbsUrl()).toMatch("/view1"); 11 | }); 12 | 13 | 14 | describe('view1', function() { 15 | 16 | beforeEach(function() { 17 | browser.get('index.html#/view1'); 18 | }); 19 | 20 | 21 | it('should render view1 when user navigates to /view1', function() { 22 | expect(element.all(by.css('[ng-view] p')).first().getText()). 23 | toMatch(/partial for view 1/); 24 | }); 25 | 26 | }); 27 | 28 | 29 | describe('view2', function() { 30 | 31 | beforeEach(function() { 32 | browser.get('index.html#/view2'); 33 | }); 34 | 35 | 36 | it('should render view2 when user navigates to /view2', function() { 37 | expect(element.all(by.css('[ng-view] p')).first().getText()). 38 | toMatch(/partial for view 2/); 39 | }); 40 | 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /restshop_project/ui/test/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config){ 2 | config.set({ 3 | 4 | basePath : '../', 5 | 6 | files : [ 7 | 'app/bower_components/angular/angular.js', 8 | 'app/bower_components/angular-route/angular-route.js', 9 | 'app/bower_components/angular-mocks/angular-mocks.js', 10 | 'app/js/**/*.js', 11 | 'test/unit/**/*.js' 12 | ], 13 | 14 | autoWatch : true, 15 | 16 | frameworks: ['jasmine'], 17 | 18 | browsers : ['Chrome'], 19 | 20 | plugins : [ 21 | 'karma-chrome-launcher', 22 | 'karma-firefox-launcher', 23 | 'karma-jasmine', 24 | 'karma-junit-reporter' 25 | ], 26 | 27 | junitReporter : { 28 | outputFile: 'test_out/unit.xml', 29 | suite: 'unit' 30 | } 31 | 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /restshop_project/ui/test/protractor-conf.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | allScriptsTimeout: 11000, 3 | 4 | specs: [ 5 | 'e2e/*.js' 6 | ], 7 | 8 | capabilities: { 9 | 'browserName': 'chrome' 10 | }, 11 | 12 | baseUrl: 'http://localhost:8000/app/', 13 | 14 | framework: 'jasmine', 15 | 16 | jasmineNodeOpts: { 17 | defaultTimeoutInterval: 30000 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /restshop_project/ui/test/unit/controllersSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jasmine specs for controllers go here */ 4 | 5 | describe('controllers', function(){ 6 | beforeEach(module('restShopApp.controllers')); 7 | 8 | 9 | it('should ....', inject(function($controller) { 10 | //spec body 11 | var myCtrl1 = $controller('MyCtrl1', { $scope: {} }); 12 | expect(myCtrl1).toBeDefined(); 13 | })); 14 | 15 | it('should ....', inject(function($controller) { 16 | //spec body 17 | var myCtrl2 = $controller('MyCtrl2', { $scope: {} }); 18 | expect(myCtrl2).toBeDefined(); 19 | })); 20 | }); 21 | -------------------------------------------------------------------------------- /restshop_project/ui/test/unit/directivesSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jasmine specs for directives go here */ 4 | 5 | describe('directives', function() { 6 | beforeEach(module('restShopApp.directives')); 7 | 8 | describe('app-version', function() { 9 | it('should print current version', function() { 10 | module(function($provide) { 11 | $provide.value('version', 'TEST_VER'); 12 | }); 13 | inject(function($compile, $rootScope) { 14 | var element = $compile('')($rootScope); 15 | expect(element.text()).toEqual('TEST_VER'); 16 | }); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /restshop_project/ui/test/unit/filtersSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jasmine specs for filters go here */ 4 | 5 | describe('filter', function() { 6 | beforeEach(module('restShopApp.filters')); 7 | 8 | 9 | describe('interpolate', function() { 10 | beforeEach(module(function($provide) { 11 | $provide.value('version', 'TEST_VER'); 12 | })); 13 | 14 | 15 | it('should replace VERSION', inject(function(interpolateFilter) { 16 | expect(interpolateFilter('before %VERSION% after')).toEqual('before TEST_VER after'); 17 | })); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /restshop_project/ui/test/unit/servicesSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jasmine specs for services go here */ 4 | 5 | describe('service', function() { 6 | beforeEach(module('restShopApp.services')); 7 | 8 | 9 | describe('version', function() { 10 | it('should return current version', inject(function(version) { 11 | expect(version).toEqual('0.1'); 12 | })); 13 | }); 14 | }); 15 | --------------------------------------------------------------------------------