diff --git a/backup.sh b/backup.sh new file mode 100644 index 0000000..c8cada6 --- /dev/null +++ b/backup.sh @@ -0,0 +1,3 @@ +date=$(date +%Y-%m-%d"_"%H_%M_%S) +docker-compose exec web python3 manage.py dumpdata --natural-foreign --exclude auth.permission --exclude contenttypes --indent 1 | gzip > backups/djangodump_${date}.json.gz +docker-compose exec db pg_dump -U partdoc partdoc | gzip > backups/pgdump_${date}.sql.gz diff --git a/docker-compose.yml b/docker-compose.yml index bd96ac5..42c885a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ services: working_dir: /app command: python3 ./manage.py runserver 0.0.0.0:8000 ports: - - 8080:8000 + - 8081:8000 depends_on: - db db: diff --git a/partdoc/Dockerfile b/partdoc/Dockerfile index 6c63e63..664fa89 100644 --- a/partdoc/Dockerfile +++ b/partdoc/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.10 +FROM alpine:3.11 ADD requirements.txt / RUN apk add --update --no-cache python3 py3-psycopg2 && \ diff --git a/partdoc/parts/admin.py b/partdoc/parts/admin.py index 0b7d4e4..362a605 100644 --- a/partdoc/parts/admin.py +++ b/partdoc/parts/admin.py @@ -1,9 +1,9 @@ from django.contrib import admin -from .models import Brand, Product, Version, Sketch, Part, Usage, BrandedPart, ProductUsage +from .models import Brand, Product, Version, Sketch, Part, Usage, BrandedPart, ProductUsage, Cart, CartEntry, Shop # admin.site.register([Brand, Product, Version, Sketch, Part, Usage, BrandedPart, ProductUsage]) -admin.site.register([Brand, Product, Version, Sketch]) +admin.site.register([Brand, Product, Version, Sketch, Shop, CartEntry]) class BrandedPartInline(admin.StackedInline): @@ -51,7 +51,18 @@ class BrandedPartAdmin(admin.ModelAdmin): autocomplete_fields = ["parts"] +class CartEntryInline(admin.StackedInline): + model = CartEntry + extra = 1 + raw_id_fields = ("origins",) + +class CartAdmin(admin.ModelAdmin): + inlines = [CartEntryInline] + #search_fields = ["number"] + #autocomplete_fields = ["parts"] + admin.site.register(Usage, UsageAdmin) admin.site.register(ProductUsage, ProductUsageAdmin) admin.site.register(Part, PartAdmin) admin.site.register(BrandedPart, BrandedPartAdmin) +admin.site.register(Cart, CartAdmin) diff --git a/partdoc/parts/models.py b/partdoc/parts/models.py index fd51915..d5667bc 100644 --- a/partdoc/parts/models.py +++ b/partdoc/parts/models.py @@ -1,5 +1,5 @@ from django.db import models, transaction - +from django.db.models import Sum class PartModel(models.Model): creation = models.DateTimeField(auto_now_add=True) @@ -157,3 +157,49 @@ class ProductUsage(PartModel): def __str__(self): no = self.usage.part.number if self.usage else "--(part.number)--" return "ProductUsage: " + self.product.name + " @ " + str(self.quantity) + "; " + str(no) + + + +class Shop(PartModel): + name = models.CharField(max_length=512) + url = models.URLField(null=True, blank=True) + + def __str__(self): + return self.name + +class Cart(PartModel): + name = models.CharField(max_length=512) + + def __str__(self): + return self.name + + def add(self, product_usage): + part = product_usage.usage.part + entry = None + for i in self.entries.all(): + if part == i.part: + entry = i + break + if not entry: + entry = CartEntry.objects.create(cart=self) + entry.origins.add(product_usage) + entry.save() + + +class CartEntry(PartModel): + cart = models.ForeignKey(Cart, on_delete=models.CASCADE, related_name="entries") + origins = models.ManyToManyField(ProductUsage) + shop = models.ForeignKey(Shop, on_delete=models.CASCADE, null=True, blank=True) + in_cart = models.BooleanField(default=False, blank=True) + ordered = models.BooleanField(default=False, blank=True) + + @property + def part(self): + return self.origins.first().usage.part + + @property + def quantity(self): + return self.origins.aggregate(sum=Sum('quantity'))["sum"] + + def get_quantity(self): + return self.quantity diff --git a/partdoc/parts/static/parts/update_cart.js b/partdoc/parts/static/parts/update_cart.js new file mode 100644 index 0000000..648c069 --- /dev/null +++ b/partdoc/parts/static/parts/update_cart.js @@ -0,0 +1,47 @@ +function getCookie(name) { + var cookieValue = null; + if (document.cookie && document.cookie !== '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = cookies[i].trim(); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) === (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; +} + +function csrfSafeMethod(method) { + // these HTTP methods do not require CSRF protection + return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); +} +$.ajaxSetup({ + beforeSend: function(xhr, settings) { + if (!csrfSafeMethod(settings.type) && !this.crossDomain) { +var csrftoken = getCookie('csrftoken'); + xhr.setRequestHeader("X-CSRFToken", csrftoken); + xhr.setRequestHeader("X-Frame-Options", "SAMEORIGIN"); + } + } +}); + +function update_cart(pu_id){ + var cart = $("#cart")[0].value; + console.log("cart", cart); + $.ajax({ + type: "POST", + url: "/parts/cart/add/", + data: { + "cart_name": cart, + "product_usage_id": pu_id + }, + success: function(data){ + console.log(data); + } + }).done(function(msg){console.log("done" + msg);console.log(msg)}) + .fail(function(msg){console.log("fail" + msg);console.log(msg)}) + ; +} diff --git a/partdoc/parts/templates/parts/cart.html b/partdoc/parts/templates/parts/cart.html new file mode 100644 index 0000000..bb1e06f --- /dev/null +++ b/partdoc/parts/templates/parts/cart.html @@ -0,0 +1,14 @@ +

{{cart.name}}

+

entries

+ + diff --git a/partdoc/parts/templates/parts/cart_entry.html b/partdoc/parts/templates/parts/cart_entry.html new file mode 100644 index 0000000..61c4083 --- /dev/null +++ b/partdoc/parts/templates/parts/cart_entry.html @@ -0,0 +1,6 @@ +

{{cart.name}}

+

entries

+ +TODO + +{{cart.name}} diff --git a/partdoc/parts/templates/parts/part.html b/partdoc/parts/templates/parts/part.html index ca9d8b2..3655760 100644 --- a/partdoc/parts/templates/parts/part.html +++ b/partdoc/parts/templates/parts/part.html @@ -1,5 +1,7 @@ +

part {{part.number}}

-

{{part.name}}

+

{{part.get_name}}

+

{{full_name}}

diff --git a/partdoc/parts/templates/parts/products.html b/partdoc/parts/templates/parts/products.html index 9ce0ba9..a8d5b35 100644 --- a/partdoc/parts/templates/parts/products.html +++ b/partdoc/parts/templates/parts/products.html @@ -4,3 +4,11 @@
  • {{product.name}}
  • {% endfor %} + +

    Available carts

    + + diff --git a/partdoc/parts/templates/parts/sketch.html b/partdoc/parts/templates/parts/sketch.html index b03734d..c7f8ecd 100644 --- a/partdoc/parts/templates/parts/sketch.html +++ b/partdoc/parts/templates/parts/sketch.html @@ -1,13 +1,19 @@ +{% load static %} + + +

    {{sketch.name}}

    - +{% if sketch.image %} {% endif %}

    {% for brand in sketch.get_brand %}{{brand.name}},{% endfor%} - {% for product in sketch.get_product %}{{product.name}},{% endfor%}

    {{sketch.get_brand}}

    {{sketch.get_brand.name.foo}}

    + +Edit diff --git a/partdoc/parts/urls.py b/partdoc/parts/urls.py index 11c3e83..ae6af47 100644 --- a/partdoc/parts/urls.py +++ b/partdoc/parts/urls.py @@ -13,4 +13,7 @@ urlpatterns = [ path('add//sketch/', views.sketch_add, name="continue_sketch"), path('add//sketch', views.sketch_add, name="new_sketch"), path('search/part/', views.part_search, name="part_search"), + path('cart/add/', views.update_cart, name="cart_add"), + path('cart//', views.cart, name="cart"), + path('cart//entry//', views.cart_entry, name="cart_entry"), ] diff --git a/partdoc/parts/views.py b/partdoc/parts/views.py index 1d2cc21..7cd1f10 100644 --- a/partdoc/parts/views.py +++ b/partdoc/parts/views.py @@ -13,7 +13,10 @@ ADD_SKETCH_REQUIRES = ['sketch_name', 'sketch_number', ''] def index(request): product_list = Product.objects.all() - context = {'product_list': product_list} + context = { + 'product_list': product_list, + 'cart_list': Cart.objects.all() + } return render(request, 'parts/products.html', context) @@ -31,7 +34,8 @@ def sketch(request, sketch_id): def __part_view(request, part): sketches = part.usage_set.values('sketch', 'sketch_number', 'sketch__name', 'sketch__product__name', 'sketch__product__id').annotate(qty=Sum('productusage__quantity')) - context = {'part': part, 'sketches': sketches} + full_name = part.get_name(full=True) + context = {'part': part, 'sketches': sketches, 'full_name': full_name} return render(request, 'parts/part.html', context) def part(request, part_id): @@ -179,3 +183,28 @@ def get_quantity(qnty_str): def is_exact_sketchnumber(sketch_number): approximate = ("ähn", "vgl", "~", "zu") return not any([i in sketch_number for i in approximate]) + + +def update_cart(request): + if request.method == "POST": + keys = ["cart_name", "product_usage_id"] + if all([key in request.POST for key in keys]): + cart, _ = Cart.objects.get_or_create(name=request.POST["cart_name"]) + pu = ProductUsage.objects.get(usage__id=request.POST["product_usage_id"]) + cart.add(pu) + return HttpResponse('{"success": true}') + return HttpResponse('{"success": false}') + + +def cart(request, cart_id): + cart = Cart.objects.get(id=cart_id) + entries = cart.entries.all() + context = {'cart': cart, "entries": entries} + return render(request, 'parts/cart.html', context) + + +def cart_entry(request, cart_id, entry_id): + cart = Cart.objects.get(id=cart_id) + entry = CartEntry.objects.get(id=entry_id) + context = {'cart': cart, 'entry': entry} + return render(request, 'parts/cart_entry.html', context) diff --git a/partdoc/requirements.txt b/partdoc/requirements.txt index 83abc93..fb479c3 100644 --- a/partdoc/requirements.txt +++ b/partdoc/requirements.txt @@ -1,2 +1,2 @@ -django==3.0 +django==3.0.5 psycopg2==2.8.4
    Produkt