Fix #473 -- Internal name for categories and products (#900)

* Fix #473 -- Internal name for categories and products

* fix pdf renderer
This commit is contained in:
Raphael Michel
2018-05-11 12:53:25 +02:00
committed by GitHub
parent e678b52a7e
commit edeaa1333b
30 changed files with 152 additions and 55 deletions

View File

@@ -74,7 +74,7 @@ class ItemSerializer(I18nAwareModelSerializer):
class Meta:
model = Item
fields = ('id', 'category', 'name', 'active', 'description',
fields = ('id', 'category', 'name', 'internal_name', 'active', 'description',
'default_price', 'free_price', 'tax_rate', 'tax_rule', 'admission',
'position', 'picture', 'available_from', 'available_until',
'require_voucher', 'hide_without_voucher', 'allow_cancel',
@@ -129,7 +129,7 @@ class ItemCategorySerializer(I18nAwareModelSerializer):
class Meta:
model = ItemCategory
fields = ('id', 'name', 'description', 'position', 'is_addon')
fields = ('id', 'name', 'internal_name', 'description', 'position', 'is_addon')
class QuestionOptionSerializer(I18nAwareModelSerializer):

View File

@@ -24,13 +24,15 @@ class JSONExporter(BaseExporter):
'categories': [
{
'id': category.id,
'name': str(category.name)
'name': str(category.name),
'internal_name': category.internal_name
} for category in self.event.categories.all()
],
'items': [
{
'id': item.id,
'name': str(item.name),
'internal_name': str(item.internal_name),
'category': item.category_id,
'price': item.default_price,
'tax_rate': item.tax_rule.rate if item.tax_rule else Decimal('0.00'),

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-05-09 09:17
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0089_auto_20180315_1322'),
]
operations = [
migrations.AddField(
model_name='item',
name='internal_name',
field=models.CharField(blank=True,
help_text='If you set this, this will be used instead of the public name in the '
'backend.',
max_length=255, null=True, verbose_name='Internal name'),
),
migrations.AddField(
model_name='itemcategory',
name='internal_name',
field=models.CharField(blank=True,
help_text='If you set this, this will be used instead of the public name in the backend.',
max_length=255, null=True, verbose_name='Internal name'),
),
]

View File

@@ -43,6 +43,11 @@ class ItemCategory(LoggedModel):
max_length=255,
verbose_name=_("Category name"),
)
internal_name = models.CharField(
verbose_name=_("Internal name"),
help_text=_("If you set this, this will be used instead of the public name in the backend."),
blank=True, null=True, max_length=255
)
description = I18nTextField(
blank=True, verbose_name=_("Category description")
)
@@ -63,9 +68,10 @@ class ItemCategory(LoggedModel):
ordering = ('position', 'id')
def __str__(self):
name = self.internal_name or self.name
if self.is_addon:
return _('{category} (Add-On products)').format(category=str(self.name))
return str(self.name)
return _('{category} (Add-On products)').format(category=str(name))
return str(name)
def delete(self, *args, **kwargs):
super().delete(*args, **kwargs)
@@ -205,6 +211,11 @@ class Item(LoggedModel):
max_length=255,
verbose_name=_("Item name"),
)
internal_name = models.CharField(
verbose_name=_("Internal name"),
help_text=_("If you set this, this will be used instead of the public name in the backend."),
blank=True, null=True, max_length=255
)
active = models.BooleanField(
default=True,
verbose_name=_("Active"),
@@ -309,7 +320,7 @@ class Item(LoggedModel):
ordering = ("category__position", "category", "position")
def __str__(self):
return str(self.name)
return str(self.internal_name or self.name)
def save(self, *args, **kwargs):
super().save(*args, **kwargs)

View File

@@ -46,7 +46,7 @@ DEFAULT_VARIABLES = OrderedDict((
("item", {
"label": _("Product name"),
"editor_sample": _("Sample product"),
"evaluate": lambda orderposition, order, event: str(orderposition.item)
"evaluate": lambda orderposition, order, event: str(orderposition.item.name)
}),
("variation", {
"label": _("Variation name"),
@@ -62,8 +62,8 @@ DEFAULT_VARIABLES = OrderedDict((
"label": _("Product name and variation"),
"editor_sample": _("Sample product sample variation"),
"evaluate": lambda orderposition, order, event: (
'{} - {}'.format(orderposition.item, orderposition.variation)
if orderposition.variation else str(orderposition.item)
'{} - {}'.format(orderposition.item.name, orderposition.variation)
if orderposition.variation else str(orderposition.item.name)
)
}),
("item_category", {

View File

@@ -10,10 +10,6 @@ from pretix.base.models.orders import OrderFee
from pretix.base.signals import order_fee_type_name
class DummyObject:
pass
class Dontsum:
def __init__(self, value: Any):
self.value = value
@@ -158,8 +154,7 @@ def order_overview(event: Event, subevent: SubEvent=None) -> Tuple[List[Tuple[It
c[0].num_paid = tuplesum(item.num_paid for item in c[1])
# Payment fees
payment_cat_obj = DummyObject()
payment_cat_obj.name = _('Fees')
payment_cat_obj = _('Fees')
payment_items = []
if not subevent:
@@ -198,9 +193,8 @@ def order_overview(event: Event, subevent: SubEvent=None) -> Tuple[List[Tuple[It
names = dict(OrderFee.FEE_TYPES)
for pprov, total in sorted(num_total.items(), key=lambda i: i[0]):
ppobj = DummyObject()
if pprov[0] == OrderFee.FEE_TYPE_PAYMENT:
ppobj.name = '{} - {}'.format(names[pprov[0]], provider_names.get(pprov[1], pprov[1]))
ppobj = '{} - {}'.format(names[pprov[0]], provider_names.get(pprov[1], pprov[1]))
else:
name = pprov[1]
for r, resp in order_fee_type_name.send(sender=event, fee_type=pprov[0], internal_type=pprov[1]):
@@ -208,7 +202,7 @@ def order_overview(event: Event, subevent: SubEvent=None) -> Tuple[List[Tuple[It
name = resp
break
ppobj.name = '{} - {}'.format(names[pprov[0]], name)
ppobj = '{} - {}'.format(names[pprov[0]], name)
ppobj.provider = pprov[1]
ppobj.has_variations = False
ppobj.num_total = total

View File

@@ -27,6 +27,7 @@ class CategoryForm(I18nModelForm):
localized_fields = '__all__'
fields = [
'name',
'internal_name',
'description',
'is_addon'
]
@@ -90,9 +91,9 @@ class QuotaForm(I18nModelForm):
for item in items:
if len(item.variations.all()) > 0:
for v in item.variations.all():
choices.append(('{}-{}'.format(item.pk, v.pk), '{} {}'.format(item.name, v.value)))
choices.append(('{}-{}'.format(item.pk, v.pk), '{} {}'.format(item, v.value)))
else:
choices.append(('{}'.format(item.pk), item.name))
choices.append(('{}'.format(item.pk), str(item)))
self.fields['itemvars'] = forms.MultipleChoiceField(
label=_('Products'),
@@ -282,6 +283,7 @@ class ItemCreateForm(I18nModelForm):
localized_fields = '__all__'
fields = [
'name',
'internal_name',
'category',
'admission',
'default_price',
@@ -308,6 +310,7 @@ class ItemUpdateForm(I18nModelForm):
fields = [
'category',
'name',
'internal_name',
'active',
'admission',
'description',

View File

@@ -144,7 +144,7 @@ class OrderPositionAddForm(forms.Form):
choices = []
for i in order.event.items.prefetch_related('variations').all():
pname = str(i.name)
pname = str(i)
if not i.is_available():
pname += ' ({})'.format(_('inactive'))
variations = list(i.variations.all())
@@ -243,7 +243,7 @@ class OrderPositionChangeForm(forms.Form):
choices = []
for i in instance.order.event.items.prefetch_related('variations').all():
pname = str(i.name)
pname = str(i)
if not i.is_available():
pname += ' ({})'.format(_('inactive'))
variations = list(i.variations.all())

View File

@@ -102,7 +102,7 @@ class SubEventItemForm(SubEventItemOrVariationFormMixin, forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['price'].widget.attrs['placeholder'] = money_filter(self.item.default_price, self.item.event.currency, hide_currency=True)
self.fields['price'].label = str(self.item.name)
self.fields['price'].label = str(self.item)
class Meta:
model = SubEventItem
@@ -116,7 +116,7 @@ class SubEventItemVariationForm(SubEventItemOrVariationFormMixin, forms.ModelFor
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['price'].widget.attrs['placeholder'] = money_filter(self.variation.price, self.item.event.currency, hide_currency=True)
self.fields['price'].label = '{} {}'.format(str(self.item.name), self.variation.value)
self.fields['price'].label = '{} {}'.format(str(self.item), self.variation.value)
class Meta:
model = SubEventItem

View File

@@ -86,13 +86,13 @@ class VoucherForm(I18nModelForm):
itemid, varid = iv.split('-')
i = self.instance.event.items.get(pk=itemid)
v = i.variations.get(pk=varid)
choices.append(('%d-%d' % (i.pk, v.pk), '%s %s' % (i.name, v.value)))
choices.append(('%d-%d' % (i.pk, v.pk), '%s %s' % (str(i), v.value)))
elif iv:
i = self.instance.event.items.get(pk=iv)
if i.variations.exists():
choices.append((str(i.pk), _('{product} Any variation').format(product=i.name)))
choices.append((str(i.pk), _('{product} Any variation').format(product=i)))
else:
choices.append((str(i.pk), str(i.name)))
choices.append((str(i.pk), str(i)))
self.fields['itemvar'].choices = choices
self.fields['itemvar'].widget = Select2ItemVarQuota(

View File

@@ -90,7 +90,7 @@
<span class="label label-warning">{% trans "unpaid" %}</span>
{% endif %}
</td>
<td>{{ e.item.name }}{% if e.variation %} {{ e.variation }}{% endif %}</td>
<td>{{ e.item }}{% if e.variation %} {{ e.variation }}{% endif %}</td>
<td>{{ e.order.email }}</td>
<td>
{% if e.addon_to %}

View File

@@ -42,7 +42,7 @@
<ul class="nav nav-second-level">
<li>
<a href="{% url 'control:event.items' organizer=request.event.organizer.slug event=request.event.slug %}"
{% if "event.items" == url_name or "event.item." in url_name or url_name == "event.item" %}class="active"{% endif %}>
{% if "event.items" == url_name or "event.item." in url_name or "event.items.add" == url_name or url_name == "event.item" %}class="active"{% endif %}>
{% trans "Products" %}</a>
</li>
<li>

View File

@@ -10,6 +10,9 @@
<fieldset>
<legend>{% trans "General information" %}</legend>
{% bootstrap_field form.name layout="control" %}
<div class="internal-name-wrapper">
{% bootstrap_field form.internal_name layout="control" %}
</div>
{% bootstrap_field form.copy_from layout="control" %}
{% bootstrap_field form.has_variations layout="control" %}
{% bootstrap_field form.category layout="control" %}

View File

@@ -9,6 +9,9 @@
<fieldset>
<legend>{% trans "General information" %}</legend>
{% bootstrap_field form.name layout="control" %}
<div class="internal-name-wrapper">
{% bootstrap_field form.internal_name layout="control" %}
</div>
{% bootstrap_field form.active layout="control" %}
{% bootstrap_field form.category layout="control" %}
{% bootstrap_field form.admission layout="control" %}

View File

@@ -12,6 +12,9 @@
<fieldset>
<legend>{% trans "General information" %}</legend>
{% bootstrap_field form.name layout="control" %}
<div class="internal-name-wrapper">
{% bootstrap_field form.internal_name layout="control" %}
</div>
{% bootstrap_field form.description layout="control" %}
{% bootstrap_field form.is_addon layout="control" %}
</fieldset>

View File

@@ -48,7 +48,7 @@
<td><strong>
{% if not i.active %}<strike>{% endif %}
<a href="
{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}">{{ i.name }}</a>
{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}">{{ i }}</a>
{% if not i.active %}</strike>{% endif %}
</strong>
</td>

View File

@@ -53,7 +53,7 @@
<td>
<ul>
{% for item in q.items.all %}
<li><a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=item.id %}">{{ item.name }}</a></li>
<li><a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=item.id %}">{{ item }}</a></li>
{% endfor %}
</ul>
</td>

View File

@@ -60,7 +60,7 @@
<td>
<ul>
{% for item in q.items.all %}
<li><a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=item.id %}">{{ item.name }}</a></li>
<li><a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=item.id %}">{{ item }}</a></li>
{% endfor %}
</ul>
</td>

View File

@@ -57,7 +57,7 @@
<div class="panel-heading">
<h3 class="panel-title">
#{{ position.positionid }}
<strong>{{ position.item.name }}</strong>
<strong>{{ position.item }}</strong>
{% if position.variation %}
{{ position.variation }}
{% endif %}

View File

@@ -39,7 +39,7 @@
<details class="panel panel-default" open>
<summary class="panel-heading">
<h4 class="panel-title">
<strong>{{ pos.item.name }}{% if pos.variation %}
<strong>{{ pos.item }}{% if pos.variation %}
{{ pos.variation }}
{% endif %}</strong>
<i class="fa fa-angle-down collapse-indicator"></i>

View File

@@ -189,7 +189,7 @@
{% else %}
#{{ line.positionid }}
{% endif %}
<strong>{{ line.item.name }}</strong>
<strong>{{ line.item }}</strong>
{% if line.variation %}
{{ line.variation }}
{% endif %}

View File

@@ -49,7 +49,7 @@
{% for tup in items_by_category %}
{% if tup.0 %}
<tr class="category">
<th>{{ tup.0.name }}</th>
<th>{{ tup.0 }}</th>
<th>{{ tup.0.num_canceled|togglesum:request.event.currency }}</th>
<th>{{ tup.0.num_refunded|togglesum:request.event.currency }}</th>
<th>{{ tup.0.num_expired|togglesum:request.event.currency }}</th>
@@ -60,7 +60,7 @@
{% endif %}
{% for item in tup.1 %}
<tr class="item {% if tup.0 %}categorized{% endif %}">
<td>{{ item.name }}</td>
<td>{{ item }}</td>
<td>
<a href="{{ listurl }}?item={{ item.id }}&amp;status=c&amp;provider={{ item.provider }}">
{{ item.num_canceled|togglesum:request.event.currency }}

View File

@@ -90,7 +90,7 @@
{% for item in items %}
<option value="{{ item.id }}"
{% if request.GET.item|add:0 == item.id %}selected="selected"{% endif %}>
{{ item.name }}
{{ item }}
</option>
{% endfor %}
</select>

View File

@@ -197,12 +197,16 @@ def itemvarquota_select2(request, **kwargs):
dt_end = make_aware(datetime.combine(dt.date(), time(hour=23, minute=59, second=59)), tz)
quotaf |= Q(subevent__date_from__gte=dt_start) & Q(subevent__date_from__lte=dt_end)
itemqs = request.event.items.prefetch_related('variations').filter(name__icontains=i18ncomp(query))
itemqs = request.event.items.prefetch_related('variations').filter(
Q(name__icontains=i18ncomp(query)) | Q(internal_name__icontains=query)
)
quotaqs = request.event.quotas.filter(quotaf).select_related('subevent')
more = False
else:
if page == 1:
itemqs = request.event.items.prefetch_related('variations').filter(name__icontains=i18ncomp(query))
itemqs = request.event.items.prefetch_related('variations').filter(
Q(name__icontains=i18ncomp(query)) | Q(internal_name__icontains=query)
)
else:
itemqs = request.event.items.none()
quotaqs = request.event.quotas.filter(name__icontains=query).select_related('subevent')
@@ -215,11 +219,11 @@ def itemvarquota_select2(request, **kwargs):
for i in itemqs:
variations = list(i.variations.all())
if variations:
choices.append((str(i.pk), _('{product} Any variation').format(product=i.name), ''))
choices.append((str(i.pk), _('{product} Any variation').format(product=i), ''))
for v in variations:
choices.append(('%d-%d' % (i.pk, v.pk), '%s %s' % (i.name, v.value), ''))
choices.append(('%d-%d' % (i.pk, v.pk), '%s %s' % (i, v.value), ''))
else:
choices.append((str(i.pk), i.name, ''))
choices.append((str(i.pk), str(i), ''))
for q in quotaqs:
if request.event.has_subevents:
choices.append(('q-%d' % q.pk,

View File

@@ -217,7 +217,7 @@ class PDFCheckinList(ReportlabExportMixin, BaseCheckinList):
'' if op.order.status != Order.STATUS_PAID else '',
op.order.code,
name,
str(op.item.name) + (" " + str(op.variation.value) if op.variation else "") + "\n" +
str(op.item) + (" " + str(op.variation.value) if op.variation else "") + "\n" +
money_filter(op.price, self.event.currency),
]
acache = {}
@@ -310,7 +310,7 @@ class CSVCheckinList(BaseCheckinList):
row = [
op.order.code,
op.attendee_name or (op.addon_to.attendee_name if op.addon_to else ''),
str(op.item.name) + (" " + str(op.variation.value) if op.variation else ""),
str(op.item) + (" " + str(op.variation.value) if op.variation else ""),
op.price,
date_format(last_checked_in.astimezone(self.event.timezone), 'SHORT_DATETIME_FORMAT')
if last_checked_in else ''

View File

@@ -90,7 +90,7 @@ class IndexView(EventPermissionRequiredMixin, ChartContainingView, TemplateView)
.annotate(cnt=Count('id')).order_by())
}
item_names = {
i.id: str(i.name)
i.id: str(i)
for i in Item.objects.filter(event=self.request.event)
}
ctx['obp_data'] = json.dumps([

View File

@@ -25,7 +25,7 @@ var ajaxErrDialog = {
"use strict";
$("#ajaxerr").html(c);
$("#ajaxerr .links").html("<a class='btn btn-default ajaxerr-close'>"
+ gettext("Close message") + "</a>");
+ gettext("Close message") + "</a>");
$("body").addClass("ajaxerr");
},
hide: function () {
@@ -45,7 +45,7 @@ $(document).ajaxError(function (event, jqXHR, settings, thrownError) {
});
var form_handlers = function (el) {
el.find(".datetimepicker").each(function() {
el.find(".datetimepicker").each(function () {
$(this).datetimepicker({
format: $("body").attr("data-datetimeformat"),
locale: $("body").attr("data-datetimelocale"),
@@ -68,7 +68,7 @@ var form_handlers = function (el) {
}
});
el.find(".datepickerfield").each(function() {
el.find(".datepickerfield").each(function () {
var opts = {
format: $("body").attr("data-dateformat"),
locale: $("body").attr("data-datetimelocale"),
@@ -104,7 +104,7 @@ var form_handlers = function (el) {
}
});
el.find(".timepickerfield").each(function() {
el.find(".timepickerfield").each(function () {
var opts = {
format: $("body").attr("data-timeformat"),
locale: $("body").attr("data-datetimelocale"),
@@ -127,7 +127,7 @@ var form_handlers = function (el) {
$(this).datetimepicker(opts);
});
el.find(".datetimepicker[data-date-after], .datepickerfield[data-date-after]").each(function() {
el.find(".datetimepicker[data-date-after], .datepickerfield[data-date-after]").each(function () {
var later_field = $(this),
earlier_field = $($(this).attr("data-date-after")),
update = function () {
@@ -144,7 +144,7 @@ var form_handlers = function (el) {
earlier_field.on("dp.change", update);
});
el.find(".datetimepicker[data-date-default], .datepickerfield[data-date-default]").each(function() {
el.find(".datetimepicker[data-date-default], .datepickerfield[data-date-default]").each(function () {
var fill_field = $(this),
default_field = $($(this).attr("data-date-default")),
show = function () {
@@ -461,7 +461,7 @@ $(function () {
} else {
$(".form-group:has(#voucher-bulk-codes-num)").addClass("has-error");
$("#voucher-bulk-codes-num").focus();
setTimeout(function() {
setTimeout(function () {
$(".form-group:has(#voucher-bulk-codes-num)").removeClass("has-error");
}, 3000);
}
@@ -469,7 +469,7 @@ $(function () {
form_handlers($("body"));
$(".qrcode-canvas").each(function() {
$(".qrcode-canvas").each(function () {
$(this).qrcode(
{
text: $.trim($($(this).attr("data-qrdata")).html())
@@ -477,7 +477,7 @@ $(function () {
);
});
$(".propagated-settings-box button[data-action=unlink]").click(function(ev) {
$(".propagated-settings-box button[data-action=unlink]").click(function (ev) {
var $box = $(this).closest(".propagated-settings-box");
$box.find(".propagated-settings-overlay").fadeOut();
$box.find("input[name=_settings_ignore]").attr("name", "decouple");
@@ -486,6 +486,7 @@ $(function () {
return true;
});
// Tables with bulk selection, e.g. subevent list
$("input[data-toggle-table]").each(function (ev) {
var $toggle = $(this);
@@ -514,6 +515,28 @@ $(function () {
});
});
// Items and categories
$(".internal-name-wrapper").each(function () {
if ($(this).find("input").val() === "") {
var $fg = $(this).find(".form-group");
$fg.hide();
var $fgl = $("<div>").addClass("form-group").append(
$("<div>").addClass("col-md-9 col-md-offset-3").append(
$("<div>").addClass("help-block").append(
$("<a>").attr("href", "#").text(
gettext("Use a different name internally")
).click(function () {
$fg.slideDown();
$fgl.slideUp();
return false;
})
)
)
);
$(this).append($fgl);
}
});
$("#ajaxerr").on("click", ".ajaxerr-close", ajaxErrDialog.hide);
moment.locale($("body").attr("data-datetimelocale"));
});