diff options
author | luxagraf <sng@luxagraf.net> | 2023-07-14 15:04:26 -0500 |
---|---|---|
committer | luxagraf <sng@luxagraf.net> | 2023-07-14 15:04:26 -0500 |
commit | 33a87ab2b62d4692c9e0450bccf203eafdd8cd80 (patch) | |
tree | a7933e366c2a70691c34210a10cccd81e3c77569 /app/products | |
parent | 9dc6e678a57203f0bc5c08f4780069b7cabdb45a (diff) |
posts: added notes for saving urls
Diffstat (limited to 'app/products')
-rw-r--r-- | app/products/admin.py | 28 | ||||
-rw-r--r-- | app/products/migrations/0001_initial.py | 68 | ||||
-rw-r--r-- | app/products/migrations/__init__.py | 0 | ||||
-rw-r--r-- | app/products/models.py | 87 | ||||
-rw-r--r-- | app/products/static/product-loader.js | 10 | ||||
-rw-r--r-- | app/products/templates/products/snippet.html | 41 | ||||
-rw-r--r-- | app/products/views.py | 15 |
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']}) |