mirror of
https://github.com/pretix/pretix.git
synced 2026-09-30 19:04:43 +00:00
Pass choice field options to JS
This commit is contained in:
@@ -1611,6 +1611,22 @@ class Question(LoggedModel):
|
||||
class ContainerType(models.TextChoices):
|
||||
ORDER = "O", _("Order")
|
||||
ORDERPOSITION = "P", _("Order position")
|
||||
|
||||
class FieldType(models.TextChoices):
|
||||
NUMBER = "N", _("Number")
|
||||
STRING = "S", _("Text (one line)")
|
||||
TEXT = "T", _("Multiline text")
|
||||
BOOLEAN = "B", _("Yes/No")
|
||||
CHOICE = "C", _("Choose one from a list")
|
||||
CHOICE_MULTIPLE = "M", _("Choose multiple from a list")
|
||||
FILE = "F", _("File upload")
|
||||
DATE = "D", _("Date")
|
||||
TIME = "H", _("Time")
|
||||
DATETIME = "W", _("Date and time")
|
||||
COUNTRYCODE = "CC", _("Country code (ISO 3166-1 alpha-2)")
|
||||
PHONENUMBER = "TEL", _("Phone number")
|
||||
|
||||
# compat
|
||||
TYPE_NUMBER = "N"
|
||||
TYPE_STRING = "S"
|
||||
TYPE_TEXT = "T"
|
||||
@@ -1623,20 +1639,8 @@ class Question(LoggedModel):
|
||||
TYPE_DATETIME = "W"
|
||||
TYPE_COUNTRYCODE = "CC"
|
||||
TYPE_PHONENUMBER = "TEL"
|
||||
TYPE_CHOICES = (
|
||||
(TYPE_NUMBER, _("Number")),
|
||||
(TYPE_STRING, _("Text (one line)")),
|
||||
(TYPE_TEXT, _("Multiline text")),
|
||||
(TYPE_BOOLEAN, _("Yes/No")),
|
||||
(TYPE_CHOICE, _("Choose one from a list")),
|
||||
(TYPE_CHOICE_MULTIPLE, _("Choose multiple from a list")),
|
||||
(TYPE_FILE, _("File upload")),
|
||||
(TYPE_DATE, _("Date")),
|
||||
(TYPE_TIME, _("Time")),
|
||||
(TYPE_DATETIME, _("Date and time")),
|
||||
(TYPE_COUNTRYCODE, _("Country code (ISO 3166-1 alpha-2)")),
|
||||
(TYPE_PHONENUMBER, _("Phone number")),
|
||||
)
|
||||
TYPE_CHOICES = FieldType.choices
|
||||
|
||||
UNLOCALIZED_TYPES = [TYPE_DATE, TYPE_TIME, TYPE_DATETIME]
|
||||
ASK_DURING_CHECKIN_UNSUPPORTED = []
|
||||
SHOW_DURING_CHECKIN_UNSUPPORTED = [TYPE_FILE]
|
||||
@@ -1652,9 +1656,8 @@ class Question(LoggedModel):
|
||||
verbose_name=_("Asked on"),
|
||||
default=ContainerType.ORDERPOSITION,
|
||||
)
|
||||
question = I18nTextField(
|
||||
# TODO(questionnaires) : to be renamed to 'internal_name'
|
||||
verbose_name=_("Question")
|
||||
question = I18nTextField( # TODO(questionnaires) : to be renamed to 'internal_name'
|
||||
verbose_name=_("Internal name"),
|
||||
)
|
||||
identifier = models.CharField(
|
||||
max_length=190,
|
||||
@@ -1676,7 +1679,7 @@ class Question(LoggedModel):
|
||||
)
|
||||
type = models.CharField(
|
||||
max_length=5,
|
||||
choices=TYPE_CHOICES,
|
||||
choices=FieldType.choices,
|
||||
verbose_name=_("Question type")
|
||||
)
|
||||
required = models.BooleanField( # TODO(questionnaires) : to be removed, -> QuestionnaireChild
|
||||
@@ -1990,18 +1993,12 @@ class QuestionOption(models.Model):
|
||||
|
||||
|
||||
class Questionnaire(LoggedModel):
|
||||
TYPE_ORDER_SALE = "OS"
|
||||
TYPE_ORDER_POSITION_SALE = "PS"
|
||||
TYPE_ORDER_POSITION_ATTENDEE_ONLY = "PA"
|
||||
TYPE_ORDER_POSITION_CHECKIN = "PC"
|
||||
TYPE_ORDER_POSITION_HIDDEN = "PH"
|
||||
TYPE_CHOICES = (
|
||||
(TYPE_ORDER_SALE, _("Order-wide, before purchase")),
|
||||
(TYPE_ORDER_POSITION_SALE, _("Per product, before purchase")),
|
||||
(TYPE_ORDER_POSITION_ATTENDEE_ONLY, _("Per product, via attendee link")),
|
||||
(TYPE_ORDER_POSITION_CHECKIN, _("Per product, at check-in")),
|
||||
(TYPE_ORDER_POSITION_HIDDEN, _("Per product, hidden")),
|
||||
)
|
||||
class QuestionnaireType(models.TextChoices):
|
||||
ORDER_SALE = "OS", _("Order-wide, before purchase")
|
||||
ORDER_POSITION_SALE = "PS", _("Per product, before purchase")
|
||||
ORDER_POSITION_ATTENDEE_ONLY = "PA", _("Per product, via attendee link")
|
||||
ORDER_POSITION_CHECKIN = "PC", _("Per product, at check-in")
|
||||
ORDER_POSITION_HIDDEN = "PH", _("Per product, hidden")
|
||||
event = models.ForeignKey(
|
||||
Event,
|
||||
related_name="questionnaires",
|
||||
@@ -2013,7 +2010,7 @@ class Questionnaire(LoggedModel):
|
||||
)
|
||||
type = models.CharField(
|
||||
max_length=5,
|
||||
choices=TYPE_CHOICES,
|
||||
choices=QuestionnaireType.choices,
|
||||
verbose_name=_("Questionnaire type")
|
||||
)
|
||||
items = models.ManyToManyField(
|
||||
@@ -2041,15 +2038,15 @@ class Questionnaire(LoggedModel):
|
||||
|
||||
|
||||
class QuestionnaireChild(LoggedModel):
|
||||
SYSTEM_QUESTION_CHOICES = (
|
||||
('attendee_name_parts', _('Attendee name')),
|
||||
('attendee_email', _('Attendee email')),
|
||||
('company', _('Company')),
|
||||
('street', _('Street')),
|
||||
('zipcode', _('ZIP code')),
|
||||
('city', _('City')),
|
||||
('country', _('Country')),
|
||||
)
|
||||
class SystemQuestion(models.TextChoices):
|
||||
ATTENDEE_NAME_PARTS = 'attendee_name_parts', _('Attendee name')
|
||||
ATTENDEE_EMAIL = 'attendee_email', _('Attendee email')
|
||||
COMPANY = 'company', _('Company')
|
||||
STREET = 'street', _('Street')
|
||||
ZIPCODE = 'zipcode', _('ZIP code')
|
||||
CITY = 'city', _('City')
|
||||
COUNTRY = 'country', _('Country')
|
||||
|
||||
questionnaire = models.ForeignKey(
|
||||
Questionnaire,
|
||||
related_name="children",
|
||||
@@ -2067,7 +2064,7 @@ class QuestionnaireChild(LoggedModel):
|
||||
)
|
||||
system_datafield = models.CharField(
|
||||
max_length=25,
|
||||
choices=SYSTEM_QUESTION_CHOICES,
|
||||
choices=SystemQuestion.choices,
|
||||
null=True, blank=True,
|
||||
)
|
||||
required = models.BooleanField(
|
||||
|
||||
@@ -28,10 +28,15 @@
|
||||
|
||||
{{ request.event.settings.locales|json_script:"event_locales" }}
|
||||
{{ questionnaire_type_choices|json_script:"questionnaire_type_choices" }}
|
||||
{{ question_type_choices|json_script:"question_type_choices" }}
|
||||
{{ system_question_choices|json_script:"system_question_choices" }}
|
||||
|
||||
{% url "control:event.items.questions.edit" organizer=request.event.organizer.slug event=request.event.slug question=0 as datafield_edit_url %}
|
||||
{{ datafield_edit_url|json_script:"datafield_edit_url" }}
|
||||
|
||||
{% url "control:event.items.questions.add" organizer=request.event.organizer.slug event=request.event.slug as datafield_create_url %}
|
||||
{{ datafield_create_url|json_script:"datafield_create_url" }}
|
||||
|
||||
<div id="questionnaires-editor">
|
||||
<!-- Vue app mount point -->
|
||||
</div>
|
||||
|
||||
@@ -59,6 +59,7 @@ from django.views.decorators.http import require_http_methods
|
||||
from django.views.generic import FormView, ListView, TemplateView, View
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
from django_countries.fields import Country
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
from pretix.api.serializers.item import (
|
||||
ItemAddOnSerializer, ItemBundleSerializer, ItemProgramTimeSerializer,
|
||||
@@ -67,11 +68,11 @@ from pretix.api.serializers.item import (
|
||||
from pretix.base.forms import I18nFormSet
|
||||
from pretix.base.models import (
|
||||
CartPosition, Item, ItemCategory, ItemProgramTime, ItemVariation, LogEntry,
|
||||
OrderPosition, Question, QuestionAnswer, QuestionOption, Quota,
|
||||
OrderPosition, Question, QuestionAnswer, QuestionOption, QuestionnaireChild, Quota,
|
||||
SeatCategoryMapping, Voucher,
|
||||
)
|
||||
from pretix.base.models.event import SubEvent
|
||||
from pretix.base.models.items import ItemAddOn, ItemBundle, ItemMetaValue
|
||||
from pretix.base.models.items import ItemAddOn, ItemBundle, ItemMetaValue, Questionnaire
|
||||
from pretix.base.services.quotas import QuotaAvailability
|
||||
from pretix.base.services.tickets import invalidate_cache
|
||||
from pretix.base.signals import quota_availability
|
||||
@@ -436,7 +437,7 @@ class QuestionList(ListView):
|
||||
template_name = 'pretixcontrol/items/questions.html'
|
||||
|
||||
def get_queryset(self):
|
||||
return self.request.event.questions
|
||||
return self.request.event.questions.all()
|
||||
|
||||
|
||||
class QuestionDelete(EventPermissionRequiredMixin, CompatDeleteView):
|
||||
@@ -706,10 +707,28 @@ class QuestionCreate(EventPermissionRequiredMixin, QuestionMixin, CreateView):
|
||||
return ret
|
||||
|
||||
|
||||
def textchoices_to_json(choices, event):
|
||||
return [(c.name, c.value, i18n_all(event.settings.locales, LazyI18nString.from_gettext(c.label).data)) for c in choices]
|
||||
|
||||
|
||||
def i18n_all(locales, data):
|
||||
out = {}
|
||||
for locale in locales:
|
||||
out[locale] = data[locale]
|
||||
return out
|
||||
|
||||
|
||||
class QuestionnairesEditor(EventPermissionRequiredMixin, TemplateView):
|
||||
permission = 'can_change_items'
|
||||
template_name = 'pretixcontrol/items/questionnaires.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['questionnaire_type_choices'] = textchoices_to_json(Questionnaire.QuestionnaireType, self.request.event)
|
||||
ctx['system_question_choices'] = textchoices_to_json(QuestionnaireChild.SystemQuestion, self.request.event)
|
||||
ctx['question_type_choices'] = textchoices_to_json(Question.FieldType, self.request.event)
|
||||
return ctx
|
||||
|
||||
|
||||
class QuotaQueryMixin:
|
||||
|
||||
|
||||
@@ -1,97 +1,71 @@
|
||||
/* global gettext, pgettext */
|
||||
|
||||
export function i18n_any(data) {
|
||||
if (!data) return null;
|
||||
const preferred = document.body.getAttribute("data-pretixlocale");
|
||||
if (data[preferred]) return data[preferred];
|
||||
return Object.values(data)[0];
|
||||
export function i18n_any (data) {
|
||||
if (!data) return null
|
||||
if (data instanceof LazyI18nString) data = data.data;
|
||||
const preferred = document.body.getAttribute('data-pretixlocale')
|
||||
if (data[preferred]) return data[preferred]
|
||||
return Object.values(data)[0]
|
||||
}
|
||||
|
||||
function freezeRec(o) {
|
||||
function freezeRec (o) {
|
||||
return Object.freeze(Object.fromEntries(Object.entries(o).map(([k, v]) => [k, v && Object.getPrototypeOf(v) === Object.prototype ? freezeRec(v) : v])))
|
||||
}
|
||||
|
||||
export function localeComp(fn) {
|
||||
return function(a, b) {
|
||||
return fn(a).localeCompare(fn(b));
|
||||
export function localeComp (fn) {
|
||||
return function (a, b) {
|
||||
return fn(a).localeCompare(fn(b))
|
||||
}
|
||||
}
|
||||
export function numericComp(fn) {
|
||||
return function(a, b) {
|
||||
return fn(a) - fn(b);
|
||||
export function numericComp (fn) {
|
||||
return function (a, b) {
|
||||
return fn(a) - fn(b)
|
||||
}
|
||||
}
|
||||
export function pick(key) {
|
||||
return function(obj) {
|
||||
return obj[key];
|
||||
export function pick (key) {
|
||||
return function (obj) {
|
||||
return obj[key]
|
||||
}
|
||||
}
|
||||
export function sort(array, ...orderBy) {
|
||||
array.sort(function(a, b) {
|
||||
for(let comp of orderBy) {
|
||||
const result = comp(a, b);
|
||||
export function sort (array, ...orderBy) {
|
||||
array.sort(function (a, b) {
|
||||
for (let comp of orderBy) {
|
||||
const result = comp(a, b)
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
return result
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return 0
|
||||
})
|
||||
}
|
||||
export function *groupBy(array, key) {
|
||||
let lastKey, lastArray;
|
||||
for(const x of array){
|
||||
const k = key(x);
|
||||
export function *groupBy (array, key) {
|
||||
let lastKey, lastArray
|
||||
for (const x of array) {
|
||||
const k = key(x)
|
||||
if (lastKey !== k || !lastArray) {
|
||||
if (lastArray) {
|
||||
yield [lastKey, lastArray];
|
||||
yield [lastKey, lastArray]
|
||||
}
|
||||
lastKey = k; lastArray = [x];
|
||||
lastKey = k; lastArray = [x]
|
||||
} else {
|
||||
lastArray.push(x);
|
||||
lastArray.push(x)
|
||||
}
|
||||
}
|
||||
if (lastArray) {
|
||||
yield [lastKey, lastArray];
|
||||
yield [lastKey, lastArray]
|
||||
}
|
||||
}
|
||||
|
||||
export const QUESTION_TYPE = {
|
||||
NUMBER: "N",
|
||||
STRING: "S",
|
||||
TEXT: "T",
|
||||
BOOLEAN: "B",
|
||||
CHOICE: "C",
|
||||
CHOICE_MULTIPLE: "M",
|
||||
FILE: "F",
|
||||
DATE: "D",
|
||||
TIME: "H",
|
||||
DATETIME: "W",
|
||||
COUNTRYCODE: "CC",
|
||||
PHONENUMBER: "TEL",
|
||||
};
|
||||
function fromJsonScript (id) {
|
||||
return JSON.parse(document.getElementById(id).textContent)
|
||||
}
|
||||
|
||||
export const _ = x => x;
|
||||
export const QUESTION_TYPE = Object.fromEntries(fromJsonScript('question_type_choices').map(([name, value, label]) => [name, value]))
|
||||
export const QUESTION_TYPE_LABEL = Object.fromEntries(fromJsonScript('question_type_choices').map(([name, value, label]) => [name, i18n_any(label)]))
|
||||
|
||||
export const QUESTION_TYPE_LABEL = {
|
||||
NUMBER: _("Number"),
|
||||
STRING: _("Text (one line)"),
|
||||
TEXT: _("Multiline text"),
|
||||
BOOLEAN: _("Yes/No"),
|
||||
CHOICE: _("Choose one from a list"),
|
||||
CHOICE_MULTIPLE: _("Choose multiple from a list"),
|
||||
FILE: _("File upload"),
|
||||
DATE: _("Date"),
|
||||
TIME: _("Time"),
|
||||
DATETIME: _("Date and time"),
|
||||
COUNTRYCODE: _("Country code (ISO 3166-1 alpha-2)"),
|
||||
PHONENUMBER: _("Phone number"),
|
||||
};
|
||||
export const SYSTEM_DATAFIELDS = freezeRec(Object.fromEntries(fromJsonScript('system_question_choices').map(([name, value, label]) => [
|
||||
value, { id: value, question: label, type: value === 'country' ? QUESTION_TYPE.COUNTRYCODE : QUESTION_TYPE.STRING }
|
||||
])))
|
||||
|
||||
export const SYSTEM_DATAFIELDS = freezeRec({
|
||||
'attendee_name_parts': { label: _('Attendee name'), type: QUESTION_TYPE.STRING },
|
||||
'attendee_email': { label: _('Attendee email'), type: QUESTION_TYPE.STRING },
|
||||
'company': { label: _('Company'), type: QUESTION_TYPE.STRING },
|
||||
'street': { label: _('Street'), type: QUESTION_TYPE.STRING },
|
||||
'zipcode': { label: _('ZIP code'), type: QUESTION_TYPE.STRING },
|
||||
'city': { label: _('City'), type: QUESTION_TYPE.STRING },
|
||||
'country': { label: _('Country'), type: QUESTION_TYPE.COUNTRYCODE },
|
||||
});
|
||||
export const QUESTIONNAIRE_TYPE = Object.fromEntries(fromJsonScript('questionnaire_type_choices').map(([name, value, label]) => [name, value]))
|
||||
export const QUESTIONNAIRE_TYPE_LABEL = Object.fromEntries(fromJsonScript('questionnaire_type_choices').map(([name, value, label]) => [name, i18n_any(label)]))
|
||||
|
||||
Reference in New Issue
Block a user