Skip to content
Snippets Groups Projects
Commit ea867aee authored by Hugo Carrasco's avatar Hugo Carrasco
Browse files

Merge branch 'feature/mathiafl/item-stock-intergration' into 'master'

Feature/mathiafl/item stock intergration

See merge request tdt4242_7/secondExercise!16
parents 4c4744a5 25995b61
No related branches found
No related tags found
No related merge requests found
Pipeline #
from django.core.mail import send_mail
from django.template.loader import render_to_string
class OutOfStockError(Exception):
pass
def send_confirmation_email(email, context):
msg_plain = render_to_string('email/confirmation.txt', context)
msg_html = render_to_string('email/confirmation.html', context)
send_mail(
"Receipt on Order id " + context['cart'].pk,
msg_plain,
'donotreply@group7webshop.com',
[email],
fail_silently=True,
html_message=msg_html,
)
\ No newline at end of file
......@@ -10,6 +10,7 @@ https://docs.djangoproject.com/en/2.0/ref/settings/
"""
import os
import dj_database_url
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
......@@ -32,19 +33,20 @@ LOGIN_REDIRECT_URL = 'index'
# Application definition
INSTALLED_APPS = [
'webshop',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Disable Django's own staticfiles handling in favour of WhiteNoise, for
# greater consistency between gunicorn and `./manage.py runserver`. See:
# http://whitenoise.evans.io/en/stable/django.html#using-whitenoise-in-development
'whitenoise.runserver_nostatic',
'django.contrib.staticfiles',
'django_filters',
'mathfilters',
'webshop',
]
MIDDLEWARE = [
......@@ -137,3 +139,11 @@ STATICFILES_DIRS = [
# Simplified static file serving.
# https://warehouse.python.org/project/whitenoise/
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = os.environ.get('EMAIL_USER')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_PASSWORD')
EMAIL_PORT = 587
......@@ -2,9 +2,13 @@ from django.contrib import admin
from .models import *
class ItemAdmin(admin.ModelAdmin):
list_display = ('name', 'brand', 'stock', 'price')
# Register your models here.
admin.site.register(Brand)
admin.site.register(Tag)
admin.site.register(Item)
admin.site.register(Item, ItemAdmin)
admin.site.register(DiscountPercentage)
admin.site.register(DiscountPackage)
# Generated by Django 2.0.2 on 2018-03-28 15:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('webshop', '0004_auto_20180304_2228'),
]
operations = [
migrations.AddField(
model_name='item',
name='stock',
field=models.PositiveIntegerField(default=0),
),
]
import math
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.contrib.auth.models import User
from secondExercise.helper_functions import OutOfStockError
# Create your models here.
......@@ -32,6 +35,7 @@ class Item(models.Model):
price = models.DecimalField(max_digits=10, decimal_places=2)
brand = models.ForeignKey("Brand", on_delete=models.CASCADE, default=1)
tag = models.ManyToManyField("Tag")
stock = models.PositiveIntegerField(default=0)
def __str__(self):
return self.name
......@@ -93,8 +97,7 @@ class Cart(models.Model):
def add_item(self, item):
try:
order = self.items.get(item=item)
order.quantity += 1
order.save()
order.add_quantity(1)
except ObjectDoesNotExist:
self.items.add(Order.objects.create(item=item))
self.save()
......@@ -103,8 +106,7 @@ class Cart(models.Model):
try:
order = self.items.get(item=item)
if amount >= 1:
order.quantity = amount
order.save()
order.update_quantity(amount)
elif amount <= 0:
self.delete_item(item)
self.save()
......@@ -130,6 +132,11 @@ class Cart(models.Model):
except ObjectDoesNotExist:
pass
def update_stock(self):
for order in self.items.all():
order.item.stock -= order.quantity
order.item.save()
def get_total(self):
total = 0
for order in self.items.all()[:]:
......@@ -141,6 +148,21 @@ class Order(models.Model):
item = models.ForeignKey('Item', on_delete=models.CASCADE)
quantity = models.IntegerField(default=1)
# Increases quantity by amount if in stock.
def add_quantity(self, amount):
if self.item.stock < (self.quantity + amount):
raise OutOfStockError()
self.quantity += amount
self.save()
# Sets quantity by amount if in stock, or deletes order.
def update_quantity(self, quantity):
if self.item.stock < quantity:
raise OutOfStockError()
self.quantity = quantity
self.save()
# Calculates the order total
def get_order_total(self):
try:
......@@ -156,13 +178,6 @@ class Order(models.Model):
discount_total = self.item.price * int(self.quantity / discount.buyx) * discount.fory
rest_total = self.item.price * (self.quantity % discount.buyx)
return discount_total + rest_total
temp_quantity = self.quantity
total = 0
while temp_quantity - discount.buyx >= 0:
total += self.item.price * discount.fory
temp_quantity -= discount.buyx
total += self.item.price * temp_quantity
return round(total, 2)
except ObjectDoesNotExist:
# No discount
return round(self.item.price * self.quantity, 2)
......@@ -16,7 +16,7 @@
.cart_item {
display: grid;
grid-template-columns: 3fr 1fr 1fr 1fr 1fr;
grid-template-columns: 3fr 1fr 1fr 2fr 1fr 1fr;
justify-items: center;
margin: 1rem;
}
......@@ -27,7 +27,7 @@
}
.horizontal_line {
height: 0px;
height: 0;
border-bottom: 1px solid black;
}
......@@ -53,3 +53,11 @@
.strikethrough {
text-decoration: line-through;
}
.is-invalid{
border-color: rgba(175, 17, 14, 0.87) !important;
}
.invalid-help-text {
color: rgba(175, 17, 14, 0.87) !important;;
}
\ No newline at end of file
......@@ -5,8 +5,13 @@
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'main.css' %}">
{% block css %}{% endblock %}
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.bundle.min.js" integrity="sha384-feJI7QwhOS+hwpX2zkaeJQjeiwlhOP+SdQDqhgvvo1DsjtiSQByFdThsxO669S2D" crossorigin="anonymous"></script>
{% block js %}{% endblock %}
<title>Web shop</title>
</head>
<body>
......@@ -46,6 +51,22 @@
</div>
</nav>
{% block content %}{% endblock content %}
{% if messages %}
<div class="message-list">
{% for message in messages %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
{{ message.message }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
{% endfor %}
</div>
{% endif %}
</div>
<script>
$('.alert').alert('dispose');
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Confirmation</title>
</head>
<body>
<h3>Hello, dear {{ user.username }}</h3>
<h4>Your order has been completed and it consist of:</h4>
<table>
<tr>
<td>Item</td>
<td>Amount</td>
</tr>
{% for item in cart.items.all %}
<tr>
<td>
{{ item.item.name }}
</td>
<td>
{{ item.quantity }}
</td>
</tr>
{% endfor %}
</table>
<p> For a total of {{ total }}NOK</p>
<p>------</p>
<strong>Best regards, webshopguys!</strong>
</body>
</html>
\ No newline at end of file
Hello {{user.username}}!
Your order at Webshop has been completed
Your order consist of:
{% for item in cart.items.all %}
{{ item.item.name }} - {{ item.quantity }}
{% endfor %}
For a total of {{ total }}NOK
Best regards, Webshop!
......@@ -11,7 +11,7 @@
<script type="text/javascript">
// Allows the user to press the enter key when changing the quantity
function handle_input(event, item_pk, quantity) {
if (event.key = "Enter") {
if (event.key === "Enter") {
updateQuantity(item_pk, quantity);
}
}
......@@ -32,6 +32,24 @@ function updateQuantity(item_pk, quantity) {
let location = "{% url 'update_item_in_cart_base' %}" + item_pk + "/" + quantity + "/";
window.location.href = location;
}
function buyCart () {
let email = document.getElementById('InputEmail1');
if (validateEmail(email.value)) {
window.location.href = "{% url 'buy_cart_base' %}" + email.value + '/';
}
else {
email.classList.toggle('is-invalid');
document.getElementById('emailHelp').innerHTML = 'Please provide a valid email address';
document.getElementById('emailHelp').classList.add('invalid-help-text');
}
}
function validateEmail(email) {
const re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
}
</script>
{% endblock %}
......@@ -40,6 +58,7 @@ function updateQuantity(item_pk, quantity) {
<div class="cart_item">
<span>Shopping cart</span>
<span>Quantity</span>
<span>Stock</span>
<span>Price</span>
<span>Total</span>
<span>Delete</span>
......@@ -58,9 +77,10 @@ function updateQuantity(item_pk, quantity) {
</span>
<div class="item_quantity_selector">
<button class="btn_quantity_selector" onclick="subtractQuantity({{order.item.pk}})">-</button>
<input class="input_quantity_selector" type="number" maxlength="3" min="1" name="{{order.item.pk}}-quantity" value={{order.quantity}} onsubmit="updateQuantity({{order.item.pk}}, this.value)" onkeydown="handle_input(event, {{order.item.pk}}, this.value)">
<input class="input_quantity_selector" type="number" maxlength="3" min="1" name="{{order.item.pk}}-quantity" value={{order.quantity}} onkeydown="handle_input(event, {{order.item.pk}}, this.value)">
<button class="btn_quantity_selector" onclick="addQuantity({{order.item.pk}})">+</button>
</div>
<span>{{order.item.stock}}</span>
{% if order.item.get_item_percentage_price %}
<span><span class="strikethrough">{{order.item.price}}</span> {{order.item.get_item_percentage_price}} NOK</span>
{% else %}
......@@ -71,7 +91,41 @@ function updateQuantity(item_pk, quantity) {
</div>
<div class="horizontal_line"></div>
{% endfor %}
<h4>Subtotal ({{ item_amount }} items): {{ total_price }} NOK <a href="{% url 'buy_cart' %}">[Buy Cart]</a></h4>
<h4>Subtotal ({{ item_amount }} items): {{ total_price }} NOK </h4>
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal">
Buy cart
</button>
{% endif %}
</div>
<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<label for="InputEmail1">Please specify email adress to send reciept</label>
<input type="email" class="form-control" id="InputEmail1" aria-describedby="emailHelp"
placeholder="Enter email" required data-error-msg="The email is required in valid format!">
<small id="emailHelp" class="form-text">We'll never share your email with anyone else.</small>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Abort</button>
<button type="submit" class="btn btn-primary" onclick="buyCart()">Send reciept</button>
</div>
</div>
</div>
</div>
{% endblock %}
from django.urls import path
from django.conf.urls import url
from django.contrib.auth import views as auth_views
from django.urls import path
from . import views
......@@ -13,7 +13,8 @@ urlpatterns = [
url(r'^logout/$', auth_views.logout, {'template_name': 'logged_out.html'}, name='logout'),
path('<int:item_id>/', views.detail, name='detail'),
path('cart/', views.cart, name='cart'),
path('cart/buy/', views.buy_cart, name='buy_cart'),
path('cart/buy/', views.buy_cart, name='buy_cart_base'),
path('cart/buy/<str:email>/', views.buy_cart, name='buy_cart'),
path('add/<int:item_pk>/', views.add_item_to_cart, name='add_item_to_cart'),
path('update/', views.update_item_in_cart, name='update_item_in_cart_base'),
path('update/<int:item_pk>/<int:amount>/', views.update_item_in_cart, name='update_item_in_cart'),
......
import django_filters
from django import forms
from django.contrib import messages
from django.contrib.auth import login, authenticate
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.views import login
from django.shortcuts import render, redirect, get_list_or_404, get_object_or_404
from django.template import Template, Context, loader
from django.contrib.auth.decorators import login_required
from django import forms
import django_filters
from .models import Item, Brand, Tag, Cart
from django.http import Http404
from django.shortcuts import redirect, get_object_or_404
from django.shortcuts import render
from django.http import HttpResponse
from secondExercise.helper_functions import OutOfStockError, send_confirmation_email
from .models import Item, Brand, Tag, Cart
@login_required
def index(request):
item_list = ItemsFilter(request.GET, queryset=Item.objects.all()[:5])
item_list = ItemsFilter(request.GET, queryset=Item.objects.filter(stock__gt=0)[:5])
context = {
'item_list': item_list,
}
......@@ -82,14 +81,20 @@ def cart(request):
def add_item_to_cart(request, item_pk):
item = get_object_or_404(Item, pk=item_pk)
cart, created = Cart.objects.get_or_create(owner=request.user, active=True)
try:
cart.add_item(item)
except OutOfStockError:
messages.add_message(request, messages.ERROR, 'The stock of this item can\'t support your desired order')
return redirect('cart')\
@login_required()
def update_item_in_cart(request, item_pk, amount):
item = get_object_or_404(Item, pk=item_pk)
cart, created = Cart.objects.get_or_create(owner=request.user, active=True)
try:
cart.update_item(item, amount)
except OutOfStockError:
messages.add_message(request, messages.ERROR, 'The stock of this item can\'t support your desired order')
return redirect('cart')\
@login_required()
......@@ -109,14 +114,17 @@ def delete_item_from_cart(request, item_pk):
@login_required
def buy_cart(request):
def buy_cart(request, email):
cart = get_object_or_404(Cart, owner=request.user, active=True)
if(cart.get_total() <= 0):
return redirect('cart')
cart.update_stock()
context = {
'total': cart.get_total(),
'cart': cart
'cart': cart,
'user': request.user
}
send_confirmation_email(email, context)
cart.active = False
cart.save()
return render(request, 'webshop/reciept.html', context)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment