summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorluxagraf <sng@luxagraf.net>2019-11-05 11:15:30 -0500
committerluxagraf <sng@luxagraf.net>2019-11-05 11:15:30 -0500
commitb1057c9dc9ff6b6e1786d31e3401ad9571eb7f1d (patch)
treec3247bc40c63bf5df1c6fecee75a81b7c2ebafde
parent725e3d2f798d228a386466823490b6f25647a4db (diff)
added products
-rw-r--r--app/products/admin.py22
-rw-r--r--app/products/migrations/0001_initial.py36
-rw-r--r--app/products/migrations/0002_auto_20191008_0841.py29
-rw-r--r--app/products/migrations/0003_auto_20191008_0941.py30
-rw-r--r--app/products/migrations/__init__.py0
-rw-r--r--app/products/models.py89
-rw-r--r--app/products/static/product-loader.js47
-rw-r--r--app/products/templates/products/snippet.html26
-rw-r--r--app/products/views.py15
-rw-r--r--design/templates/admin/insert_products.html82
10 files changed, 376 insertions, 0 deletions
diff --git a/app/products/admin.py b/app/products/admin.py
new file mode 100644
index 0000000..d8059f2
--- /dev/null
+++ b/app/products/admin.py
@@ -0,0 +1,22 @@
+from django.contrib import admin
+from .models import Brand, Product
+
+
+@admin.register(Product)
+class ProductAdmin(admin.ModelAdmin):
+ list_display = ('name', 'admin_thumbnail', 'rating', 'pub_date')
+ search_fields = ['name', 'body_markdown']
+ list_filter = ('rating', 'pub_date')
+
+ class Media:
+ js = ('next-prev-links.js',)
+
+
+@admin.register(Brand)
+class BrandAdmin(admin.ModelAdmin):
+ list_display = ('name', )
+ search_fields = ['name',]
+ list_filter = ('pub_date',)
+
+ class Media:
+ js = ('next-prev-links.js',)
diff --git a/app/products/migrations/0001_initial.py b/app/products/migrations/0001_initial.py
new file mode 100644
index 0000000..eb2a822
--- /dev/null
+++ b/app/products/migrations/0001_initial.py
@@ -0,0 +1,36 @@
+# Generated by Django 2.2.6 on 2019-10-08 07:46
+
+from django.db import migrations, models
+import products.models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Product',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=200)),
+ ('slug', models.CharField(max_length=50)),
+ ('pub_date', models.DateTimeField()),
+ ('body_markdown', models.TextField(blank=True)),
+ ('body_html', models.TextField(blank=True, null=True)),
+ ('primary_offer_url', models.CharField(max_length=400)),
+ ('primary_offer_price', models.IntegerField()),
+ ('secondary_offer_url', models.CharField(blank=True, max_length=400, null=True)),
+ ('secondar_offer_price', models.IntegerField(blank=True, null=True)),
+ ('rating', models.IntegerField()),
+ ('is_public', models.BooleanField(default=True)),
+ ('image', models.FileField(blank=True, null=True, upload_to=products.models.get_upload_path)),
+ ],
+ options={
+ 'ordering': ('-pub_date',),
+ },
+ ),
+ ]
diff --git a/app/products/migrations/0002_auto_20191008_0841.py b/app/products/migrations/0002_auto_20191008_0841.py
new file mode 100644
index 0000000..11b2d59
--- /dev/null
+++ b/app/products/migrations/0002_auto_20191008_0841.py
@@ -0,0 +1,29 @@
+# Generated by Django 2.2.6 on 2019-10-08 08:41
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('products', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Brand',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=200)),
+ ('slug', models.CharField(max_length=50)),
+ ('pub_date', models.DateTimeField(auto_now_add=True)),
+ ],
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='brand',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='products.Brand'),
+ preserve_default=False,
+ ),
+ ]
diff --git a/app/products/migrations/0003_auto_20191008_0941.py b/app/products/migrations/0003_auto_20191008_0941.py
new file mode 100644
index 0000000..085fd32
--- /dev/null
+++ b/app/products/migrations/0003_auto_20191008_0941.py
@@ -0,0 +1,30 @@
+# Generated by Django 2.2.6 on 2019-10-08 09:41
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('products', '0002_auto_20191008_0841'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='product',
+ old_name='secondar_offer_price',
+ new_name='secondary_offer_price',
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='primary_offer_retailer',
+ field=models.CharField(default='Amazon', max_length=400),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='product',
+ name='secondary_offer_retailer',
+ field=models.CharField(default='Amazon', max_length=400),
+ preserve_default=False,
+ ),
+ ]
diff --git a/app/products/migrations/__init__.py b/app/products/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/products/migrations/__init__.py
diff --git a/app/products/models.py b/app/products/models.py
new file mode 100644
index 0000000..bf33db4
--- /dev/null
+++ b/app/products/models.py
@@ -0,0 +1,89 @@
+import os
+from PIL import Image
+from django.db import models
+from django.db.models.signals import post_save
+from django.contrib.sitemaps import Sitemap
+from django.dispatch import receiver
+from django.urls import reverse
+from django.apps import apps
+from django.utils.html import format_html
+from django.conf import settings
+from django.template.defaultfilters import slugify
+
+from photos.utils import resize_image
+from utils.util import render_images, render_products, parse_video, markdown_to_html
+
+
+def get_upload_path(self, filename):
+ return "images/products/%s" % (filename)
+
+class Brand(models.Model):
+ name = models.CharField(max_length=200)
+ slug = models.CharField(max_length=50)
+ pub_date = models.DateTimeField(auto_now_add=True)
+
+ def __str__(self):
+ return self.name
+
+class Product(models.Model):
+ name = models.CharField(max_length=200)
+ brand = models.ForeignKey(Brand, on_delete=models.CASCADE)
+ slug = models.CharField(max_length=50)
+ pub_date = models.DateTimeField()
+ body_markdown = models.TextField(blank=True)
+ body_html = models.TextField(null=True, blank=True)
+ primary_offer_retailer = models.CharField(max_length=400)
+ primary_offer_url = models.CharField(max_length=400)
+ primary_offer_price = models.IntegerField()
+ secondary_offer_retailer = models.CharField(max_length=400)
+ secondary_offer_url = models.CharField(max_length=400, blank=True, null=True)
+ secondary_offer_price = models.IntegerField(blank=True, null=True)
+ rating = models.IntegerField()
+ is_public = models.BooleanField(default=True)
+ image = models.FileField(upload_to=get_upload_path, null=True, blank=True)
+
+ class Meta:
+ ordering = ('-pub_date',)
+
+ def __str__(self):
+ return self.name
+
+ @property
+ def get_previous_admin_url(self):
+ n = self.get_previous_by_read_date()
+ return reverse('admin:%s_%s_change' % (self._meta.app_label, self._meta.model_name), args=[n.id])
+
+ @property
+ def get_next_admin_url(self):
+ model = apps.get_model(app_label=self._meta.app_label, model_name=self._meta.model_name)
+ try:
+ return reverse('admin:%s_%s_change' % (self._meta.app_label, self._meta.model_name), args=[self.get_next_by_read_date().pk])
+ except model.DoesNotExist:
+ return ''
+
+ def admin_thumbnail(self):
+ return format_html('<img src="%s" width="100" style="width:100px" />' % (self.get_image_url()))
+ admin_thumbnail.short_description = 'Thumbnail'
+
+ def get_full_name(self):
+ return "%s %s" % (self.brand.name, self.name)
+
+ def get_image_url(self):
+ return '%sbook-covers/%s' % (settings.IMAGES_URL, self.image.name.split('/')[-1])
+
+ def save(self, *args, **kwargs):
+ md = render_images(self.body_markdown)
+ prods = render_products(md)
+ self.body_html = markdown_to_html(prods)
+ super(Product, self).save()
+
+
+@receiver(post_save, sender=Product)
+def post_save_events(sender, update_fields, created, instance, **kwargs):
+ #base_path = "%s/%s/" % (settings.MEDIA_ROOT, "/".join(str(i) for i in instance.image.name.split('/')[:-1]))
+ #filename, file_extension = os.path.splitext(instance.image.path)
+ #img = Image.open(instance.image.path)
+ #resize_image(img, None, 160, 78, base_path, "%s_tn%s" % (filename.split('/')[-1], file_extension))
+ #resize_image(img, None, 650, 78, base_path, "%s_small%s" % (filename.split('/')[-1], file_extension))
+ pass
+
diff --git a/app/products/static/product-loader.js b/app/products/static/product-loader.js
new file mode 100644
index 0000000..20ec0d6
--- /dev/null
+++ b/app/products/static/product-loader.js
@@ -0,0 +1,47 @@
+function add_products(){
+ var el = document.getElementById("id_body_markdown");
+ if (el){
+ var iframe = '<iframe frameborder="0" style="border: #dddddd 1px solid;margin-left: 20px;width:330px; height:720px;" src="/luxproduct/insert/?textarea='+el.id+'"></iframe>';
+ el.insertAdjacentHTML('afterend', iframe);
+ }
+
+ var featured_image = document.getElementById("id_featured_image")
+
+ if (featured_image) {
+ featured_image.querySelectorAll('li').forEach(function(element) {
+ var cur = element.dataset.imageid;
+ var loop = Number(element.dataset.loopcounter);
+ if (cur != "") {
+ if (loop <= 100) {
+ console.log(loop);
+ var request = new XMLHttpRequest();
+ request.open('GET', '/photos/luximage/data/admin/tn/'+cur+'/', true);
+ request.onload = function() {
+ if (request.status >= 200 && request.status < 400) {
+ var data = JSON.parse(request.responseText);
+ var el = element.getElementsByTagName('label')[0];
+ url = "url('"+data['url']+"');";
+ //console.log(url);
+ el.style.backgroundImage = 'url('+data["url"]+')';
+
+ //console.log(el.style);
+ } else {
+ console.log("server error", request.statusText);
+ }
+ };
+ request.onerror = function() {
+ console.log("error on request");
+ };
+ request.send();
+ }
+ }
+ });
+ }
+
+}
+document.addEventListener("DOMContentLoaded", function(event) {
+ add_products();
+ md = document.forms["entry_form"].elements["body_markdown"];
+ md.style.maxHeight = "300rem";
+ md.style.maxWidth = "300rem";
+});
diff --git a/app/products/templates/products/snippet.html b/app/products/templates/products/snippet.html
new file mode 100644
index 0000000..be5d874
--- /dev/null
+++ b/app/products/templates/products/snippet.html
@@ -0,0 +1,26 @@
+<div itemscope itemtype="http://schema.org/Product">
+ <meta itemprop="brand" content="{{object.brand.name}}" />
+ <h4 class="product-link" itemprop="name">{{object.get_full_name}}: (<span itemprop="offers" itemscope itemtype="http://schema.org/Offer">
+ <a href="{{object.primary_offer_url}}" title="buy the {{object.get_full_name}} for ${{object.primary_offer_price}}" itemprop="url" rel="nofollow">
+ <span itemprop="priceCurrency" content="USD">$</span><span
+ itemprop="price" content="{{object.primary_offer_price}}">{{object.primary_offer_price}}</span> {{object.primary_offer_retailer}}</a>
+ <link itemprop="availability" href="http://schema.org/InStock" />
+ </span>)
+ {% if object.secondary_offer_url %}(<span itemprop="offers" itemscope itemtype="http://schema.org/Offer">
+ <a href="{{object.secondary_offer_url}}" title="buy the {{object.get_full_name}} for ${{object.secondary_offer_price}}" itemprop="url">
+ <span itemprop="priceCurrency" content="USD">$</span><span
+ itemprop="price" content="{{object.secondary_offer_price}}">{{object.secondary_offer_price}}</span> {{object.secondary_offer_retailer}}</a>
+ <link itemprop="availability" href="http://schema.org/InStock" />
+ </span>){% endif %}</h4>
+ <div itemprop="review" itemscope itemtype="http://schema.org/Review">
+ <meta itemprop="name" content="{{object.get_full_name}}" />
+ <meta itemprop="author" content="Scott Gilbertson" />
+ <meta itemprop="datePublished" content="{{object.pub_date}}" />
+ <span itemprop="reviewRating" itemscope itemtype="http://schema.org/Rating">
+ <meta itemprop="worstRating" content = "1"/>
+ <meta itemprop="ratingValue" content="{{object.rating}}" />
+ <meta itemprop="bestRating" content="10" />
+ </span>
+ <meta itemprop="description" content="{{object.body_markdown}}" />
+ </div>
+</div>
diff --git a/app/products/views.py b/app/products/views.py
new file mode 100644
index 0000000..30be542
--- /dev/null
+++ b/app/products/views.py
@@ -0,0 +1,15 @@
+from django.shortcuts import render
+from .models import Product
+
+
+def insert_products(request):
+ """
+ The view that handles the admin insert products feature
+ """
+ object_list = Product.objects.all()
+ #object_list = sorted(
+ # chain(images, videos, audio),
+ # key=lambda instance: instance.pub_date,
+ # reverse=True
+ #)
+ return render(request, 'admin/insert_products.html', {'object_list': object_list, 'textarea_id': request.GET['textarea']})
diff --git a/design/templates/admin/insert_products.html b/design/templates/admin/insert_products.html
new file mode 100644
index 0000000..b38389e
--- /dev/null
+++ b/design/templates/admin/insert_products.html
@@ -0,0 +1,82 @@
+{% load get_image_by_size %}
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<head>
+<style>
+.item-wrapper {
+ display: flex;
+ flex-wrap: wrap;
+ margin-bottom: 6px;
+ font-family: sans-serif;
+ font-size: 11px;
+ padding: 6px 0 4px;
+ border-top: #ccc 1px solid;
+}
+.item { }
+.item img { margin: 0 0 4px 0;}
+h5 {
+ max-width: 180px;
+ font-size: 18px;
+ line-height: 22px;
+ margin: 12px;
+}
+.actions {
+ list-style-type: none;
+ margin: 0 0 0 10px;
+ padding: 0;
+}
+.actions li {
+ margin: 3px 0;
+}
+.actions a {
+ color: #666;
+}
+</style>
+<script>
+function buildImage(id, prod_name) {
+ html = '<div id="product-'+id+'">'+prod_name+'</div>';
+ return html;
+}
+function openInNewTab(url) {
+ var win = window.open(url, '_blank');
+ win.focus();
+ return false;
+}
+</script>
+</head>
+<body>
+ <input type="button" value="Refresh" onClick="window.location.reload()">
+ <button id="add_id_image" onClick="window.parent.open('/admin/products/product/add/?_to_field=id&amp;_popup=1')">Add Product
+ <img src="/static/admin/img/icon-addlink.svg" alt="Add">
+ </button>
+ <div class="up-wrapper">{% for object in object_list %}
+ <div class="item-wrapper images">
+ <h5 class="item" >
+ {{object.get_full_name}}
+ </h5>
+ <ul class="actions">
+ <li><a data-id="{{object.id}}" data-name="{{object.get_full_name}}" onclick="insertProduct(this);return false;" href="#" >Insert Product</a></li>
+ <li><a onclick='openInNewTab("/admin/photos/luximage/{{object.pk}}/change/");' href="#">Edit Image</a></li>
+ </ul>
+ </div>
+{% endfor %}
+</div>
+
+<!-- "next page" action -->
+<a class="nextPage browse right"></a>
+<script>
+function insertProduct(item) {
+ var code = buildImage(item.dataset.id, item.dataset.name);
+ var el = parent.document.getElementById('{{textarea_id}}');
+ var start = el.selectionStart;
+ var end = el.selectionEnd;
+ var text = el.value;
+ var before = text.substring(0, start);
+ var after = text.substring(end, text.length);
+ el.value = (before + code + after);
+ el.selectionStart = el.selectionEnd = start + code.length;
+ el.focus();
+}
+</script>
+</body>
+</html>