summaryrefslogtreecommitdiff
path: root/app/products
diff options
context:
space:
mode:
authorluxagraf <sng@luxagraf.net>2023-07-14 15:04:26 -0500
committerluxagraf <sng@luxagraf.net>2023-07-14 15:04:26 -0500
commit33a87ab2b62d4692c9e0450bccf203eafdd8cd80 (patch)
treea7933e366c2a70691c34210a10cccd81e3c77569 /app/products
parent9dc6e678a57203f0bc5c08f4780069b7cabdb45a (diff)
posts: added notes for saving urls
Diffstat (limited to 'app/products')
-rw-r--r--app/products/admin.py28
-rw-r--r--app/products/migrations/0001_initial.py68
-rw-r--r--app/products/migrations/__init__.py0
-rw-r--r--app/products/models.py87
-rw-r--r--app/products/static/product-loader.js10
-rw-r--r--app/products/templates/products/snippet.html41
-rw-r--r--app/products/views.py15
7 files changed, 249 insertions, 0 deletions
diff --git a/app/products/admin.py b/app/products/admin.py
new file mode 100644
index 0000000..a25b84e
--- /dev/null
+++ b/app/products/admin.py
@@ -0,0 +1,28 @@
+from django.contrib import admin
+
+from .models import Brand, Product
+from utils.widgets import AdminImageWidget, LGEntryForm
+
+
+@admin.register(Product)
+class ProductAdmin(admin.ModelAdmin):
+ form = LGEntryForm
+ list_display = ('name', 'admin_thumbnail', 'rating', 'date_created')
+ search_fields = ['name', 'body_markdown']
+ list_filter = ('rating', 'date_created')
+
+ class Media:
+ js = ('image-loader.js', 'next-prev-links.js')
+ css = {
+ "all": ("my_styles.css",)
+ }
+
+
+@admin.register(Brand)
+class BrandAdmin(admin.ModelAdmin):
+ list_display = ('name', )
+ search_fields = ['name',]
+ list_filter = ('date_created',)
+
+ 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..028bc04
--- /dev/null
+++ b/app/products/migrations/0001_initial.py
@@ -0,0 +1,68 @@
+# Generated by Django 4.2.2 on 2023-07-10 18:02
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Brand',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=200)),
+ ('slug', models.CharField(max_length=50)),
+ ('date_created', models.DateTimeField(auto_now_add=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Retailer',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=200)),
+ ('slug', models.CharField(max_length=50)),
+ ('date_created', models.DateTimeField(auto_now_add=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='ProductLink',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('url', models.CharField(blank=True, max_length=255, null=True)),
+ ('date_last_checked', models.DateTimeField(blank=True, null=True)),
+ ('date_created', models.DateTimeField(auto_now_add=True)),
+ ('is_active', models.BooleanField(default=True)),
+ ('product', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='products.brand')),
+ ('retailer', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='products.retailer')),
+ ],
+ options={
+ 'ordering': ('date_last_checked',),
+ },
+ ),
+ migrations.CreateModel(
+ name='Product',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=200)),
+ ('wired_name', models.CharField(max_length=200)),
+ ('normalized_name', models.CharField(max_length=200)),
+ ('slug', models.CharField(max_length=250)),
+ ('date_created', models.DateTimeField()),
+ ('body_markdown', models.TextField(blank=True)),
+ ('body_html', models.TextField(blank=True, null=True)),
+ ('wired_price', models.IntegerField()),
+ ('lowest_price_ever', models.IntegerField(null=True)),
+ ('rating', models.IntegerField()),
+ ('brand', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='products.brand')),
+ ],
+ options={
+ 'ordering': ('-date_created',),
+ },
+ ),
+ ]
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..58a0264
--- /dev/null
+++ b/app/products/models.py
@@ -0,0 +1,87 @@
+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
+
+
+class Retailer(models.Model):
+ name = models.CharField(max_length=200)
+ slug = models.CharField(max_length=50)
+ date_created = models.DateTimeField(auto_now_add=True)
+
+ def __str__(self):
+ return self.name
+
+
+class Brand(models.Model):
+ name = models.CharField(max_length=200)
+ slug = models.CharField(max_length=50)
+ date_created = models.DateTimeField(auto_now_add=True)
+
+ def __str__(self):
+ return self.name
+
+
+class Product(models.Model):
+ name = models.CharField(max_length=200)
+ wired_name = models.CharField(max_length=200)
+ normalized_name = models.CharField(max_length=200)
+ brand = models.ForeignKey(Brand, null=True, on_delete=models.SET_NULL)
+ slug = models.CharField(max_length=250)
+ date_created = models.DateTimeField()
+ body_markdown = models.TextField(blank=True)
+ body_html = models.TextField(null=True, blank=True)
+ wired_price = models.IntegerField()
+ lowest_price_ever = models.IntegerField(null=True)
+ rating = models.IntegerField()
+
+ class Meta:
+ ordering = ('-date_created',)
+
+ 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.featured_image.get_thumbnail_url()))
+ admin_thumbnail.short_description = 'Thumbnail'
+
+ def get_full_name(self):
+ return "%s %s" % (self.brand.name, self.name)
+
+ def save(self, *args, **kwargs):
+ super(Product, self).save()
+
+
+class ProductLink(models.Model):
+ retailer = models.ForeignKey(Retailer, null=True, on_delete=models.SET_NULL)
+ url = models.CharField(max_length=255, blank=True, null=True)
+ date_last_checked = models.DateTimeField(blank=True, null=True)
+ date_created = models.DateTimeField(auto_now_add=True)
+ product = models.ForeignKey(Brand, null=True, on_delete=models.SET_NULL)
+ is_active = models.BooleanField(default=True)
+
+ class Meta:
+ ordering = ('date_last_checked',)
+
+ def __str__(self):
+ return self.url
diff --git a/app/products/static/product-loader.js b/app/products/static/product-loader.js
new file mode 100644
index 0000000..6d04b61
--- /dev/null
+++ b/app/products/static/product-loader.js
@@ -0,0 +1,10 @@
+function add_products(){
+ var el = document.getElementById("images_frame");
+ 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);
+ }
+}
+document.addEventListener("DOMContentLoaded", function(event) {
+ add_products();
+});
diff --git a/app/products/templates/products/snippet.html b/app/products/templates/products/snippet.html
new file mode 100644
index 0000000..3fc9f6f
--- /dev/null
+++ b/app/products/templates/products/snippet.html
@@ -0,0 +1,41 @@
+{% load get_image_by_size %}
+{% load get_image_width %}
+{% with image=object.featured_image %}
+<div itemscope itemtype="http://schema.org/Product" class="product-card">
+ <meta itemprop="brand" content="{{object.brand.name}}" />
+ <figure itemscope itemtype="http://schema.org/ImageObject" class="picfull">
+ <a href="{% get_image_by_size image 'original' %}" title="view larger image">
+ <img class="u-photo" itemprop="contentUrl" sizes="(max-width: 750px) 100vw, (min-width: 751) 750px" srcset="{% for size in image.sizes.all%}{% get_image_by_size image size.name %} {{size.width}}w{% if forloop.last%}"{%else%}, {%endif%}{%endfor%}{% for size in image.sizes.all%}{%if not forloop.first and not forloop.last%} src="{% get_image_by_size image size.name%}"{%endif%}{%endfor%} alt="{{image.alt}} photographed by {% if image.photo_credit_source %}{{image.photo_credit_source}}{%else%}luxagraf{%endif%}" >
+ </a>
+ <figcaption>{% if image.caption %}{{image.caption|safe}}{% endif %}{% if image.photo_credit_source %}{%if image.caption %} | {%endif%}image: {% if image.photo_credit_url %}<a href="{{image.photo_credit_url}}" itemprop="author">{%endif%}{{image.photo_credit_source|lower}}{% if image.photo_credit_url %}</a>{%endif%}{%endif%}
+ </figcaption>
+ </figure>
+ <div class="buy-btn-wrapper">
+ <h4 class="product-header" itemprop="name">{{object.get_full_name}}</h4>
+ <h5 class="product-link" 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}} from {{object.primary_offer_retailer.get_primary_offer_retailer_display}}" itemprop="url" rel="nofollow">
+ Buy Now ({{object.get_primary_offer_retailer_display}}
+ <span itemprop="priceCurrency" content="USD">$</span><span itemprop="price" content="{{object.primary_offer_price}}">{{object.primary_offer_price}}</span>)
+ </a>
+ <link itemprop="availability" href="http://schema.org/InStock" />
+ </h5>{% if object.secondary_offer_url %}
+ <h5 class="product-link" 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}} from {{object.secondary_offer_retailer.get_secondary_offer_retailer_display}}" itemprop="url">
+ Buy Now ({{object.get_secondary_offer_retailer_display}}
+ <span itemprop="priceCurrency" content="USD">$</span><span itemprop="price" content="{{object.secondary_offer_price}}">{{object.secondary_offer_price}}</span>)
+ </a>
+ <link itemprop="availability" href="http://schema.org/InStock" />
+ </h5>{% endif %}
+ </div>
+ <span 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>
+{% endwith %}
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']})