Skip to content

Commit

Permalink
Merge pull request #244 from iragm/lot-label-rework
Browse files Browse the repository at this point in the history
Lot label rework
  • Loading branch information
iragm authored Oct 17, 2024
2 parents 5923eef + 1f3e909 commit 716d801
Show file tree
Hide file tree
Showing 19 changed files with 663 additions and 232 deletions.
6 changes: 5 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,16 @@ RUN apt-get update && \
default-libmysqlclient-dev \
cron \
nano \
# python3-pip \
# python3-cffi \
# python3-brotli \
libpango-1.0-0 \
libpangoft2-1.0-0 \
# libheif dependencies
libheif-dev \
&& \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

RUN pip install pip-tools

# cron setup
Expand Down
103 changes: 101 additions & 2 deletions auctions/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2398,11 +2398,15 @@ def __init__(self, *args, **kwargs):
Div(
Div(
"preset",
css_class="col-sm-8",
css_class="col-sm-7",
),
Div(
"empty_labels",
css_class="col-sm-4",
css_class="col-sm-3",
),
Div(
"print_border",
css_class="col-sm-2",
),
css_class="row",
),
Expand Down Expand Up @@ -2612,3 +2616,98 @@ def clean(self):
# image_source = cleaned_data.get("image_source")
# if image and not image_source:
# self.add_error('image_source', "Is this your picture?")


class LabelPrintFieldsForm(forms.Form):
lot_number = forms.BooleanField(
label="Lot number",
help_text="Lot number is required",
disabled=True,
required=False,
initial=True,
)

def __init__(self, *args, **kwargs):
self.auction = kwargs.pop("auction", None)
super().__init__(*args, **kwargs)

self.available_fields = [
# if updating this:
# also update models.Auction.label_print_fields if a new field should be enabled by default
# update views.LotLabelView.get_context_data and put the field in either the first or second column
{
"value": "qr_code",
"description": "QR Code",
"tooltip": "Contains a link to view each lot. Use your phone's camera to scan.",
},
{
"value": "lot_name",
"description": "Lot name",
"tooltip": "<span class='text-warning'>Recommended</span>, otherwise people may put the label on the wrong lot",
},
{"value": "category", "description": "Category", "tooltip": ""},
{
"value": "donation_label",
"description": "Donation",
"tooltip": "Mark (D) on any lots that are a donation",
},
{
"value": "min_bid_label",
"description": "Minimum bid",
"tooltip": "Min bid is disabled in this auction, this will not do anything"
if self.auction.reserve_price == "disable"
else "Will only be displayed if the lot has a minimum bid set. <span class='text-warning'>Recommended</span>",
},
{
"value": "buy_now_label",
"description": "Buy now price",
"tooltip": "Buy now is disabled in this auction, this will not do anything"
if self.auction.buy_now == "disable"
else "Will only be displayed if the lot has a buy now price set. <span class='text-warning'>Recommended</span>",
},
{"value": "quantity_label", "description": "Quantity", "tooltip": ""},
{
"value": "auction_date",
"description": "Auction date",
"tooltip": f"For record keeping of when lots were acquired, show auction date. It will appear as {self.auction.date_start.strftime('%b %Y')}",
},
{"value": "seller_name", "description": "Seller's name", "tooltip": ""},
{
"value": "seller_email",
"description": "Seller's email",
"tooltip": "Not recommended, this allows buyers to contact the seller directly. The club should mediate disputes.",
},
{
"value": "description_label",
"description": "Description",
"tooltip": "Not recommended, as descriptions can be very long.",
},
]

label_print_fields = self.auction.label_print_fields if self.auction else ""
selected_fields = label_print_fields.split(",")

# Iterate over available_fields and create form fields
for field in self.available_fields:
field_value = field["value"]
self.fields[field_value] = forms.BooleanField(
label=field["description"],
required=False,
initial=field_value in selected_fields,
help_text=field.get("tooltip", ""),
)

# Set up Crispy Form helper
self.helper = FormHelper()
self.helper.form_method = "post"
self.helper.layout = Layout(
HTML("<h4>Select fields to print:</h4>"),
Field("lot_number"),
Div(*[Field(field["value"]) for field in self.available_fields]), # Use field['value']
Submit("save", "Save", css_class="btn btn-success"), # Save button
)

def save(self):
selected_fields = [field["value"] for field in self.available_fields if self.cleaned_data.get(field["value"])]
self.auction.label_print_fields = ",".join(selected_fields)
self.auction.save()
22 changes: 22 additions & 0 deletions auctions/migrations/0155_auction_label_print_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 5.1 on 2024-10-11 19:16

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("auctions", "0154_remove_auction_notes_remove_auction_notes_rendered_and_more"),
]

operations = [
migrations.AddField(
model_name="auction",
name="label_print_fields",
field=models.CharField(
blank=True,
default="lot_name,min_bid_label,buy_now_label,quantity_label,seller_name_label,donation_label",
max_length=1000,
null=True,
),
),
]
27 changes: 27 additions & 0 deletions auctions/migrations/0156_category_name_on_label_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 5.1 on 2024-10-16 14:58

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("auctions", "0155_auction_label_print_fields"),
]

operations = [
migrations.AddField(
model_name="category",
name="name_on_label",
field=models.CharField(default="", max_length=255),
),
migrations.AlterField(
model_name="auction",
name="label_print_fields",
field=models.CharField(
blank=True,
default="qr_code,lot_name,min_bid_label,buy_now_label,quantity_label,seller_name,donation_label",
max_length=1000,
null=True,
),
),
]
60 changes: 60 additions & 0 deletions auctions/migrations/0157_userlabelprefs_print_border_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Generated by Django 5.1 on 2024-10-17 16:27

import django.core.validators
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("auctions", "0156_category_name_on_label_and_more"),
]

operations = [
migrations.AddField(
model_name="userlabelprefs",
name="print_border",
field=models.BooleanField(
default=True,
help_text="Uncheck if you plant to use peel and stick labels. Has no effect if you select thermal labels.",
),
),
migrations.AlterField(
model_name="userlabelprefs",
name="font_size",
field=models.FloatField(
default=8,
validators=[django.core.validators.MinValueValidator(5), django.core.validators.MaxValueValidator(14)],
),
),
migrations.AlterField(
model_name="userlabelprefs",
name="label_margin_right",
field=models.FloatField(
default=0.2,
validators=[
django.core.validators.MinValueValidator(0.0),
django.core.validators.MaxValueValidator(5.0),
],
),
),
migrations.AlterField(
model_name="userlabelprefs",
name="page_margin_bottom",
field=models.FloatField(default=0.45, validators=[django.core.validators.MinValueValidator(0.0)]),
),
migrations.AlterField(
model_name="userlabelprefs",
name="page_margin_left",
field=models.FloatField(default=0.18, validators=[django.core.validators.MinValueValidator(0.0)]),
),
migrations.AlterField(
model_name="userlabelprefs",
name="page_margin_right",
field=models.FloatField(default=0.18, validators=[django.core.validators.MinValueValidator(0.0)]),
),
migrations.AlterField(
model_name="userlabelprefs",
name="page_margin_top",
field=models.FloatField(default=0.55, validators=[django.core.validators.MinValueValidator(0.0)]),
),
]
67 changes: 58 additions & 9 deletions auctions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,7 @@ class Category(models.Model):
"""Picklist of species. Used for product, lot, and interest"""

name = models.CharField(max_length=255)
name_on_label = models.CharField(max_length=255, default="")

def __str__(self):
return str(self.name)
Expand Down Expand Up @@ -661,6 +662,12 @@ class Auction(models.Model):
message_users_when_lots_sell.help_text = (
"When you enter a lot number on the set lot winners screen, send a notification to any users watching that lot"
)
label_print_fields = models.CharField(
max_length=1000,
blank=True,
null=True,
default="qr_code,lot_name,min_bid_label,buy_now_label,quantity_label,seller_name,donation_label",
)

def __str__(self):
result = self.title
Expand Down Expand Up @@ -2322,10 +2329,10 @@ def seller_name(self):
@property
def seller_email(self):
"""Email of the seller of this lot"""
if self.user:
return self.user.email
if self.auctiontos_seller:
return self.auctiontos_seller.email
if self.user:
return self.user.email
return "Unknown"

@property
Expand Down Expand Up @@ -3017,6 +3024,44 @@ def create_update_invoices(self):
)
invoice.recalculate

@property
def category(self):
"""string of a shortened species_category. For labels, usually you want to use `lot.species_category` instead"""
if self.species_category and self.species_category.pk != 21:
return self.species_category.name_on_label or self.species_category
return ""

@property
def donation_label(self):
return "(D)" if self.donation else ""

@property
def min_bid_label(self):
if self.reserve_price > self.auction.minimum_bid and not self.sold:
return f"Min: ${self.reserve_price}"
return ""

@property
def buy_now_label(self):
if self.buy_now_price and not self.sold:
return f"Buy: ${self.buy_now_price}"
return ""

@property
def quantity_label(self):
if self.auction.advanced_lot_adding or self.quantity > 1:
return f"QTY: {self.quantity}"
return ""

@property
def auction_date(self):
return self.auction.date_start.strftime("%b %Y")

@property
def description_label(self):
"""Strip all html except <br> from summernote description"""
return re.sub(r"(?!<br\s*/?>)<.*?>", "", self.summernote_description)


class Invoice(models.Model):
"""
Expand Down Expand Up @@ -3523,24 +3568,28 @@ class UserLabelPrefs(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
empty_labels = models.IntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(100)])
empty_labels.help_text = "To print on partially used label sheets, print this many blank labels before printing the actual labels. Just remember to set this back to 0 when starting a new sheet of labels!"
print_border = models.BooleanField(default=True)
print_border.help_text = (
"Uncheck if you plant to use peel and stick labels. Has no effect if you select thermal labels."
)
page_width = models.FloatField(default=8.5, validators=[MinValueValidator(1), MaxValueValidator(100.0)])
page_height = models.FloatField(default=11, validators=[MinValueValidator(1), MaxValueValidator(100.0)])
label_width = models.FloatField(default=2.51, validators=[MinValueValidator(1), MaxValueValidator(100.0)])
label_height = models.FloatField(default=0.98, validators=[MinValueValidator(0.4), MaxValueValidator(50.0)])
label_margin_right = models.FloatField(default=0.2, validators=[MinValueValidator(0.1), MaxValueValidator(5.0)])
label_margin_right = models.FloatField(default=0.2, validators=[MinValueValidator(0.0), MaxValueValidator(5.0)])
label_margin_bottom = models.FloatField(default=0.02, validators=[MinValueValidator(0.0), MaxValueValidator(5.0)])
page_margin_top = models.FloatField(default=0.55, validators=[MinValueValidator(0.1)])
page_margin_bottom = models.FloatField(default=0.45, validators=[MinValueValidator(0.1)])
page_margin_left = models.FloatField(default=0.18, validators=[MinValueValidator(0.1)])
page_margin_right = models.FloatField(default=0.18, validators=[MinValueValidator(0.05)])
font_size = models.FloatField(default=8, validators=[MinValueValidator(5), MaxValueValidator(25)])
page_margin_top = models.FloatField(default=0.55, validators=[MinValueValidator(0.0)])
page_margin_bottom = models.FloatField(default=0.45, validators=[MinValueValidator(0.0)])
page_margin_left = models.FloatField(default=0.18, validators=[MinValueValidator(0.0)])
page_margin_right = models.FloatField(default=0.18, validators=[MinValueValidator(0.0)])
font_size = models.FloatField(default=8, validators=[MinValueValidator(5), MaxValueValidator(14)])
UNITS = (
("in", "Inches"),
("cm", "Centimeters"),
)
unit = models.CharField(max_length=20, choices=UNITS, blank=False, null=False, default="in")
PRESETS = (
("sm", "Small (Avery 5160)"),
("sm", "Small (Avery 5160) (Not recommended)"),
("lg", "Large (Avery 18262)"),
("thermal_sm", 'Thermal 3"x2"'),
("custom", "Custom"),
Expand Down
14 changes: 14 additions & 0 deletions auctions/templates/auction_print_setup.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block title %}Print label setup for {{ auction }}{% endblock %}
{% load static %}
{% block content %}
{% include 'auction_ribbon.html' %}
<p><small>Customize the fields that are printed on labels. These fields will appear on labels that you print, as well as labels that your users print for themselves.<br>
You can change what size labels you print on under <a href="{% url 'printing' %}?next={{ request.path }}">printing preferences</a>; your users may print their own labels in different sizes depending on their setup<br>
<div>Note that some fields will only be shown on unsold lots: sold lots will always show the winner's name and no price information (buy now/min bid).</div><br>
<div>Want some information on the label that's not listed below? Got a problem with a label, such as text that's overlapping? <a href="https://github.com/iragm/fishauctions/issues/238">Leave a comment here.</a></div><br>
{% crispy form %}
{% endblock %}
{% block extra_js %}
{% endblock %}
7 changes: 4 additions & 3 deletions auctions/templates/auction_printing.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
<p><small>This page will allow you to print labels for your auction.
<br>Some tips:<br>
<ul>
<li>It's often easier to print all labels for a single user on the <a href="/auctions/{{auction.slug}}/users">users</a> tab.</li>
<li>Set <a href="{% url 'auction_label_config' slug=auction.slug %}">what gets printed on labels</a></li>
<li>It's often easier to print all labels for a single user on the <a href="/auctions/{{auction.slug}}/users">users</a> tab</li>
<li>Encourage users to pre-register lots and print their own labels. This saves you paper and a lot of time at the registration desk.</li>
<li>Reprint damaged labels by viewing the lot's page.</li>
<li>Change your <a href="{% url 'printing' %}?next={{ request.path }}">printing preferences</a> to match the type of labels you're printing.</li>
<li>Reprint damaged labels by viewing the lot's page</li>
<li>Change your <a href="{% url 'printing' %}?next={{ request.path }}">printing preferences</a> to match the type of labels you're printing</li>
</ul>
</small></p>
<p>You've printed {{ printed_labels_count }} out of {{ all_labels_count }} labels in your auction.</p>
Expand Down
Loading

0 comments on commit 716d801

Please sign in to comment.