finance/admin: validate IBAN and show EPC-QR code for transactions (#94)

1. IBAN validation in member admin.
2. In the transaction overview, for every transaction an EPC-QR code for banking apps is generated and displayed. The (necessary) BIC field is automatically derived from the IBAN. This closes #63.

Both steps use the python library schwifty.

Reviewed-on: #94
Reviewed-by: Christian Merten <christian@merten.dev>
Co-authored-by: marius.klein <marius.klein@alpenverein-heidelberg.de>
Co-committed-by: marius.klein <marius.klein@alpenverein-heidelberg.de>
pull/98/head
marius.klein 12 months ago committed by Christian Merten
parent 19eb0953d5
commit 3b46695b49

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-01 16:23+0100\n"
"POT-Creation-Date: 2024-12-28 01:22+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,12 +18,12 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: finance/admin.py:76
#: finance/admin.py:84
#, python-format
msgid "%(name)s is already submitted."
msgstr "%(name)s ist bereits eingereicht."
#: finance/admin.py:82
#: finance/admin.py:90
#, python-format
msgid ""
"Successfully submited %(name)s. The finance department will notify the "
@ -32,23 +32,23 @@ msgstr ""
"Rechnung %(name)s erfolgreich eingereicht. Das Finanzreferat wird auf dich "
"sobald wie möglich zukommen."
#: finance/admin.py:85
#: finance/admin.py:93
msgid "Submit statement"
msgstr "Rechnung einreichen"
#: finance/admin.py:162
#: finance/admin.py:177
#, python-format
msgid "%(name)s is not yet submitted."
msgstr "%(name)s ist noch nicht eingereicht."
#: finance/admin.py:169
#: finance/admin.py:184
#, python-format
msgid "An error occured while trying to confirm %(name)s. Please try again."
msgstr ""
"Beim Abwickeln von %(name)s ist ein Fehler aufgetreten. Bitte versuche es "
"erneut."
#: finance/admin.py:173
#: finance/admin.py:188
#, python-format
msgid ""
"Successfully confirmed %(name)s. I hope you executed the associated "
@ -57,11 +57,11 @@ msgstr ""
"Erfolgreich %(name)s abgewickelt. Ich hoffe du hast die zugehörigen "
"Überweisungen ausgeführt, ich werde dich nicht nochmal erinnern."
#: finance/admin.py:180
#: finance/admin.py:195
msgid "Statement confirmed"
msgstr "Abrechnung abgewickelt"
#: finance/admin.py:186
#: finance/admin.py:201
msgid ""
"Transactions do not match the covered expenses. Please correct the mistakes "
"listed below."
@ -69,19 +69,19 @@ msgstr ""
"Überweisungen stimmen nicht mit den übernommenen Kosten überein. Bitte "
"korrigiere die unten aufgeführten Fehler."
#: finance/admin.py:191
#: finance/admin.py:206
msgid "Some transactions have no ledger configured. Please fill in the gaps."
msgstr ""
"Manche Überweisungen haben kein Geldtopf eingestellt. Bitte trage das nach."
#: finance/admin.py:200
#: finance/admin.py:215
#, python-format
msgid "Successfully rejected %(name)s. The requestor can reapply, when needed."
msgstr ""
"Die Rechnung %(name)s wurde abgelehnt. Die Person kann die Rechnung erneut "
"einstellen, wenn es benötigt wird."
#: finance/admin.py:207
#: finance/admin.py:222
#, python-format
msgid ""
"%(name)s already has transactions. Please delete them first, if you want to "
@ -90,12 +90,12 @@ msgstr ""
"%(name)s hat bereits Überweisungen. Bitte lösche diese zunächst, bevor du "
"neue generierst."
#: finance/admin.py:212
#: finance/admin.py:227
#, python-format
msgid "Successfully generated transactions for %(name)s"
msgstr "Automatisch Überweisungsträger für %(name)s generiert."
#: finance/admin.py:215
#: finance/admin.py:230
#, python-format
msgid ""
"Error while generating transactions for %(name)s. Do all bills have a payer?"
@ -103,28 +103,28 @@ msgstr ""
"Fehler beim Erzeugen der Überweisungsträger für %(name)s. Sind für alle "
"Quittungen eine bezahlende Person eingestellt? "
#: finance/admin.py:218
#: finance/admin.py:233
msgid "View submitted statement"
msgstr "Eingereichte Abrechnung einsehen"
#: finance/admin.py:230
#: finance/admin.py:245
#, python-format
msgid "Successfully reduced transactions for %(name)s."
msgstr "Überweisungsträger für %(name)s minimiert."
#: finance/admin.py:274
#: finance/admin.py:293
#, python-format
msgid "%(name)s is not yet confirmed."
msgstr "%(name)s ist noch nicht bestätigt."
#: finance/admin.py:283
#: finance/admin.py:302
#, python-format
msgid "Successfully unconfirmed %(name)s. I hope you know what you are doing."
msgstr ""
"Erfolgreich die Bestätigung von %(name)s zurückgenommen. Ich hoffe du weißt "
"was du machst."
#: finance/admin.py:288 finance/templates/admin/unconfirm_statement.html:26
#: finance/admin.py:307 finance/templates/admin/unconfirm_statement.html:26
msgid "Unconfirm statement"
msgstr "Bestätigung zurücknehmen"
@ -132,185 +132,185 @@ msgstr "Bestätigung zurücknehmen"
msgid "Finance"
msgstr "Finanzen"
#: finance/models.py:21
#: finance/models.py:24
msgid "Name"
msgstr "Name"
#: finance/models.py:27 finance/models.py:472 finance/models.py:496
#: finance/templates/admin/confirmed_statement.html:38
#: finance/models.py:30 finance/models.py:484 finance/models.py:547
#: finance/templates/admin/confirmed_statement.html:40
#: finance/templates/admin/overview_submitted_statement.html:100
msgid "Ledger"
msgstr "Geldtopf"
#: finance/models.py:28
#: finance/models.py:31
msgid "Ledgers"
msgstr "Geldtöpfe"
#: finance/models.py:48 finance/models.py:415 finance/models.py:495
#: finance/models.py:51 finance/models.py:420 finance/models.py:546
msgid "Short description"
msgstr "Kurzbeschreibung"
#: finance/models.py:51 finance/models.py:416
#: finance/models.py:54 finance/models.py:421
msgid "Explanation"
msgstr "Erklärung"
#: finance/models.py:53
#: finance/models.py:56
msgid "Associated excursion"
msgstr "Zugehörige Ausfahrt"
#: finance/models.py:58
#: finance/models.py:61
msgid "Price per night"
msgstr "Preis pro Nacht"
#: finance/models.py:60
#: finance/models.py:63
msgid "Submitted"
msgstr "Eingericht"
#: finance/models.py:61
#: finance/models.py:64
msgid "Submitted on"
msgstr "Eingereicht am"
#: finance/models.py:62
#: finance/models.py:65
msgid "Confirmed"
msgstr "Abgewickelt"
#: finance/models.py:63 finance/models.py:479
#: finance/models.py:66 finance/models.py:491
msgid "Paid on"
msgstr "Bezahlt am"
#: finance/models.py:65
#: finance/models.py:68
msgid "Created by"
msgstr "Erstellt von"
#: finance/models.py:70
#: finance/models.py:73
msgid "Submitted by"
msgstr "Eingereicht von"
#: finance/models.py:75 finance/models.py:480
#: finance/models.py:78 finance/models.py:492
msgid "Authorized by"
msgstr "Autorisiert von"
#: finance/models.py:82 finance/models.py:414 finance/models.py:475
#: finance/models.py:85 finance/models.py:419 finance/models.py:487
msgid "Statement"
msgstr "Abrechnung"
#: finance/models.py:83
#: finance/models.py:86
msgid "Statements"
msgstr "Abrechnungen"
#: finance/models.py:98
#: finance/models.py:101
#, python-format
msgid "Statement: %(excursion)s"
msgstr "Abrechnung: %(excursion)s"
#: finance/models.py:150
#: finance/models.py:153
msgid "Ready to confirm"
msgstr "Bereit zur Abwicklung"
#: finance/models.py:194
#: finance/models.py:197
#, python-format
msgid "Compensation for %(excu)s"
msgstr "Entschädigung für %(excu)s"
#: finance/models.py:327
#: finance/models.py:330
#: finance/templates/admin/overview_submitted_statement.html:78
msgid "Total"
msgstr "Gesamtbetrag"
#: finance/models.py:369
#: finance/models.py:374
msgid "Statement in preparation"
msgstr "Abrechnung in Vorbereitung"
#: finance/models.py:370
#: finance/models.py:375
msgid "Statements in preparation"
msgstr "Abrechnungen in Vorbereitung"
#: finance/models.py:389
#: finance/models.py:394
msgid "Submitted statement"
msgstr "Eingereichte Abrechnung"
#: finance/models.py:390
#: finance/models.py:395
msgid "Submitted statements"
msgstr "Eingereichte Abrechnungen"
#: finance/models.py:406
#: finance/models.py:411
msgid "Paid statement"
msgstr "Bezahlte Abrechnung"
#: finance/models.py:407
#: finance/models.py:412
msgid "Paid statements"
msgstr "Bezahlte Abrechnungen"
#: finance/models.py:418 finance/models.py:432 finance/models.py:469
#: finance/templates/admin/confirmed_statement.html:36
#: finance/models.py:423 finance/models.py:444 finance/models.py:481
#: finance/templates/admin/confirmed_statement.html:38
#: finance/templates/admin/overview_submitted_statement.html:31
#: finance/templates/admin/overview_submitted_statement.html:98
msgid "Amount"
msgstr "Betrag"
#: finance/models.py:419
#: finance/models.py:424
msgid "Paid by"
msgstr "Bezahlt von"
#: finance/models.py:421
#: finance/models.py:426
msgid "Covered"
msgstr "Übernommen"
#: finance/models.py:422
#: finance/models.py:427
msgid "Refunded"
msgstr "Ausgezahlt"
#: finance/models.py:424
#: finance/models.py:429
msgid "Proof"
msgstr "Beleg"
#: finance/models.py:435 finance/models.py:442 finance/models.py:455
#: finance/models.py:447 finance/models.py:454 finance/models.py:467
msgid "Bill"
msgstr "Ausgabe"
#: finance/models.py:436 finance/models.py:443 finance/models.py:456
#: finance/models.py:448 finance/models.py:455 finance/models.py:468
#: finance/templates/admin/overview_submitted_statement.html:26
msgid "Bills"
msgstr "Ausgaben"
#: finance/models.py:468 finance/templates/admin/confirmed_statement.html:37
#: finance/models.py:480 finance/templates/admin/confirmed_statement.html:39
#: finance/templates/admin/overview_submitted_statement.html:99
msgid "Reference"
msgstr "Verwendungszweck"
#: finance/models.py:470
#: finance/models.py:482
msgid "Recipient"
msgstr "Empfänger"
#: finance/models.py:478
#: finance/models.py:490
msgid "Paid"
msgstr "Bezahlt"
#: finance/models.py:490
#: finance/models.py:541
msgid "Transaction"
msgstr "Überweisung"
#: finance/models.py:491
#: finance/models.py:542
#: finance/templates/admin/overview_submitted_statement.html:84
msgid "Transactions"
msgstr "Überweisungen"
#: finance/templates/admin/confirmed_statement.html:17
#: finance/templates/admin/confirmed_statement.html:19
#: finance/templates/admin/overview_submitted_statement.html:17
#: finance/templates/admin/submit_statement.html:17
#: finance/templates/admin/unconfirm_statement.html:17
msgid "Home"
msgstr "Start"
#: finance/templates/admin/confirmed_statement.html:21
#: finance/templates/admin/confirmed_statement.html:23
msgid "Paiment"
msgstr "Bezahlung"
#: finance/templates/admin/confirmed_statement.html:26
#: finance/templates/admin/confirmed_statement.html:28
msgid "Paying statement"
msgstr "Rechnung bezahlen"
#: finance/templates/admin/confirmed_statement.html:29
#: finance/templates/admin/confirmed_statement.html:31
msgid ""
"The statement is valid. Please execute the following transactions and then "
"proceed by finalizing the confirmation."
@ -318,15 +318,32 @@ msgstr ""
"Die Abrechnung ist gültig. Bitte führe die folgenden Überweisungen aus und "
"fahre dann fort, indem du die Abwicklung bestätigst."
#: finance/templates/admin/confirmed_statement.html:35
#: finance/templates/admin/confirmed_statement.html:37
msgid "IBAN"
msgstr "IBAN"
#: finance/templates/admin/confirmed_statement.html:66
#: finance/templates/admin/confirmed_statement.html:41
msgid "QR Code"
msgstr "QR Code"
#: finance/templates/admin/confirmed_statement.html:61
#: finance/templates/admin/confirmed_statement.html:98
msgid "Show"
msgstr "Anzeigen"
#: finance/templates/admin/confirmed_statement.html:86
msgid "No QR code can be displayed."
msgstr "Es kann kein QR-Code angezeigt werden."
#: finance/templates/admin/confirmed_statement.html:99
msgid "Showing"
msgstr "Sichtbar"
#: finance/templates/admin/confirmed_statement.html:111
msgid "I did execute the listed transactions."
msgstr "Ich habe die aufgeführten Überweisungen ausgeführt."
#: finance/templates/admin/confirmed_statement.html:68
#: finance/templates/admin/confirmed_statement.html:113
msgid "Confirm"
msgstr "Bestätigen"

@ -15,6 +15,9 @@ from contrib.models import CommonModel
from contrib.rules import has_global_perm
from utils import cvt_to_decimal, RestrictedFileField
from schwifty import IBAN
import re
# Create your models here.
class Ledger(models.Model):
@ -495,6 +498,45 @@ class Transaction(models.Model):
def __str__(self):
return "T#{}".format(self.pk)
@staticmethod
def escape_reference(reference):
umlaut_map = {
'ä': 'ae', 'ö': 'oe', 'ü': 'ue',
'Ä': 'Ae', 'Ö': 'Oe', 'Ü': 'Ue',
'ß': 'ss'
}
pattern = re.compile('|'.join(umlaut_map.keys()))
int_reference = pattern.sub(lambda x: umlaut_map[x.group()], reference)
allowed_chars = r"[^a-z0-9 /?: .,'+-]"
clean_reference = re.sub(allowed_chars, '', int_reference, flags=re.IGNORECASE)
return clean_reference
def code(self):
if self.amount == 0:
return ""
iban = IBAN(self.member.iban, allow_invalid=True)
if not iban.is_valid:
return ""
bic = iban.bic
reference = self.escape_reference(self.reference)
# also escaping receiver as umlaute are also not allowed here
receiver = self.escape_reference(f"{self.member.prename} {self.member.lastname}")
return f"""BCD
001
1
SCT
{bic}
{receiver}
{iban}
EUR{self.amount}
{reference}"""
class Meta:
verbose_name = _('Transaction')
verbose_name_plural = _('Transactions')

@ -7,6 +7,8 @@
<script src="{% static 'admin/js/cancel.js' %}" async></script>
<script type="text/javascript" src="{% static "admin/js/vendor/jquery/jquery.js" %}"></script>
<script type="text/javascript" src="{% static "admin/js/jquery.init.js" %}"></script>
<script type="text/javascript" src="{% static "js/qrcode.js" %}"></script>
{% endblock %}
{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} admin-view
@ -36,6 +38,7 @@
<td>{% trans "Amount" %}</td>
<td>{% trans "Reference" %}</td>
<td>{% trans "Ledger" %}</td>
<td>{% trans "QR Code" %}</td>
</th>
{% for transaction in statement.transaction_set.all %}
<tr>
@ -54,11 +57,53 @@
<td>
{{ transaction.ledger }}
</td>
<td>
<a href="#" data-text="{{ transaction.code }}">{% trans "Show" %}</a>
</td>
</tr>
{% endfor %}
</table>
</p>
<div id="qr_code" style="display: none;"></div>
<script type="text/javascript">
const links = document.querySelectorAll('td a');
const imageContainer = document.getElementById('qr_code');
// Add click event listeners to all links
links.forEach(link => {
link.addEventListener('click', function (event) {
event.preventDefault(); // Prevent default link behavior
const imageText = this.getAttribute('data-text'); // Get the image path from the data attribute
imageContainer.innerHTML = '';
// Update the image element
if(imageText == "") {
imageContainer.innerHTML = '{% trans "No QR code can be displayed." %}';
} else {
var qrcode = new QRCode(imageContainer, {
text: imageText,
width: 128,
height: 128,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRCode.CorrectLevel.M
});
}
imageContainer.style.display = 'block'; // Show the image if hidden
links.forEach(link => {link.text = '{% trans "Show" %}'});
link.text = '{% trans "Showing" %}';
});
});
</script>
<form action="" method="post">
{% csrf_token %}
<p>

@ -26,7 +26,7 @@ from django.db.models import TextField, ManyToManyField, ForeignKey, Count,\
Sum, Case, Q, F, When, Value, IntegerField, Subquery, OuterRef
from django.forms import Textarea, RadioSelect, TypedChoiceField, CheckboxInput
from django.shortcuts import render
from django.core.exceptions import PermissionDenied
from django.core.exceptions import PermissionDenied, ValidationError
from .pdf import render_tex, fill_pdf_form, merge_pdfs, serve_pdf
from contrib.admin import CommonAdminInlineMixin, CommonAdminMixin
@ -43,6 +43,8 @@ from finance.models import Statement, BillOnExcursionProxy
from mailer.mailutils import send as send_mail, get_echo_link
from django.conf import settings
from utils import get_member, RestrictedFileField
from schwifty import IBAN
#from easy_select2 import apply_select2
@ -161,6 +163,20 @@ class RegistrationFilter(admin.SimpleListFilter):
'display': title
}
class MemberAdminForm(forms.ModelForm):
class Meta:
model = Member
fields = '__all__'
# check iban validity using schwifty package
def clean_iban(self):
iban_str = self.cleaned_data.get('iban')
if len(iban_str) > 0:
iban = IBAN(iban_str, allow_invalid=True)
if not iban.is_valid:
raise ValidationError(_("The entered IBAN is not valid."))
return iban_str
# Register your models here.
class MemberAdmin(CommonAdminMixin, admin.ModelAdmin):
@ -223,6 +239,8 @@ class MemberAdmin(CommonAdminMixin, admin.ModelAdmin):
actions = ['request_echo', 'invite_as_user_action']
list_per_page = 25
form = MemberAdminForm
sensitive_fields = ['iban', 'registration_form', 'comments']
field_view_permissions = {

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-27 18:18+0100\n"
"POT-Creation-Date: 2024-12-28 22:56+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,198 +18,202 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: members/admin.py:127 members/models.py:430
#: members/admin.py:129 members/models.py:430
msgid "Registration complete"
msgstr "Anmeldung vollständig"
#: members/admin.py:133
#: members/admin.py:135
msgid "True"
msgstr "Ja"
#: members/admin.py:134
#: members/admin.py:136
msgid "False"
msgstr "Nein"
#: members/admin.py:135
#: members/admin.py:137
msgid "All"
msgstr "Alle"
#: members/admin.py:185 members/admin.py:414
#: members/admin.py:178
msgid "The entered IBAN is not valid."
msgstr "Die eingegebene IBAN ist ungültig."
#: members/admin.py:201 members/admin.py:432
msgid "Contact information"
msgstr "Kontaktinformationen"
#: members/admin.py:190 members/admin.py:419
#: members/admin.py:206 members/admin.py:437
msgid "Skills"
msgstr "Fähigkeiten"
#: members/admin.py:195 members/admin.py:424
#: members/admin.py:211 members/admin.py:442
msgid "Others"
msgstr "Sonstiges"
#: members/admin.py:201 members/admin.py:429
#: members/admin.py:217 members/admin.py:447
msgid "Organizational"
msgstr "Organisatorisches"
#: members/admin.py:282
#: members/admin.py:300
msgid "Compose new mail to selected members"
msgstr "Neue Nachricht an ausgewählte Teilnehmer*innen verfassen"
#: members/admin.py:288
#: members/admin.py:306
msgid "Echo required"
msgstr "Rückmeldung erforderlich"
#: members/admin.py:290
#: members/admin.py:308
msgid "Successfully requested echo from selected members."
msgstr ""
"Rückmeldungsaufforderung erfolgreich an ausgewählte Teilnehmer*innen "
"verschickt."
#: members/admin.py:291
#: members/admin.py:309
msgid "Request echo from selected members"
msgstr "Rückmeldungsaufforderung an ausgewählte Teilnehmer*innen verschicken"
#: members/admin.py:300
#: members/admin.py:318
#, python-format
msgid "%(name)s does not have a DAV360 email address or is already registered."
msgstr "%(name)s hat keine DAV360 E-Mail Adresse oder ist bereits registriert."
#: members/admin.py:302
#: members/admin.py:320
#, python-format
msgid "Successfully invited %(name)s as user."
msgstr "Erfolgreich %(name)s aufgefordert Zugangsdaten zu wählen."
#: members/admin.py:304
#: members/admin.py:322
msgid "Successfully invited selected members to join as users."
msgstr ""
"Erfolgreich ausgewählte Teilnehmer*innen aufgefordert Zugangsdaten zu wählen."
#: members/admin.py:306
#: members/admin.py:324
msgid "Some members have been invited, others could not be invited."
msgstr ""
"Manche Teilnehmer*innen wurden eingeladen, andere konnten nicht eingeladen "
"werden."
#: members/admin.py:313 members/admin.py:330
#: members/admin.py:331 members/admin.py:348
msgid "Permission denied."
msgstr "Fehlende Berechtigungen."
#: members/admin.py:320 members/admin.py:354
#: members/admin.py:338 members/admin.py:372
#: members/templates/admin/invite_as_user.html:21
msgid "Invite as user"
msgstr "Kompass Zugangsdaten wählen lassen"
#: members/admin.py:325
#: members/admin.py:343
msgid "Invite selected members to join Kompass as users."
msgstr "Ausgewählte Teilnehmer*innen Kompass Zugangsdaten wählen lassen."
#: members/admin.py:336
#: members/admin.py:354
msgid "Member not found."
msgstr "Teilnehmer*in nicht gefunden."
#: members/admin.py:340
#: members/admin.py:358
#, python-format
msgid "%(name)s already has login data."
msgstr "%(name)s hat schon Zugangsdaten."
#: members/admin.py:345
#: members/admin.py:363
#, python-format
msgid "The configured email address for %(name)s is not an internal one."
msgstr "Die für %(name)s eingestellte E-Mail Adresse ist keine DAV360 Adresse."
#: members/admin.py:359
#: members/admin.py:377
#, python-format
msgid "%(name)s already has a pending invitation as user."
msgstr ""
"%(name)s hat bereits eine ausstehende Aufforderung Zugangsdaten zu wählen."
#: members/admin.py:377
#: members/admin.py:395
msgid "activity"
msgstr "Aktivität"
#: members/admin.py:387 members/models.py:56 members/models.py:1584
#: members/admin.py:405 members/models.py:56 members/models.py:1584
msgid "Name"
msgstr "Name"
#: members/admin.py:478
#: members/admin.py:496
msgid "Successfully requested mail confirmation from selected registrations."
msgstr "Aufforderung zur Bestätigung der Email Adresse versendet."
#: members/admin.py:479
#: members/admin.py:497
msgid "Request mail confirmation from selected registrations"
msgstr "Aufforderung zur Bestätigung der Email Adresse versenden"
#: members/admin.py:486 members/admin.py:551
#: members/admin.py:504 members/admin.py:569
#, python-format
msgid "Successfully confirmed %(name)s."
msgstr "Registrierung von %(name)s erfolgreich bestätigt."
#: members/admin.py:490 members/admin.py:554
#: members/admin.py:508 members/admin.py:572
#, python-format
msgid "Can't confirm. %(name)s has unconfirmed email addresses."
msgstr "Bestätigung nicht möglich. %(name)s hat unbestätigte Emailadressen."
#: members/admin.py:495
#: members/admin.py:513
msgid "Successfully confirmed multiple registrations."
msgstr "Erfolgreich mehrere Registrierungen bestätigt."
#: members/admin.py:497
#: members/admin.py:515
msgid ""
"Failed to confirm some registrations because of unconfirmed email addresses."
msgstr ""
"Einige Bestätigungen fehlgeschlagen, weil Emailadressen noch nicht bestätigt "
"sind."
#: members/admin.py:498
#: members/admin.py:516
msgid "Confirm selected registrations"
msgstr "Ausgewählte Registrierungen bestätigen"
#: members/admin.py:521
#: members/admin.py:539
msgid "Demote selected registrations to waiters."
msgstr "Ausgewählte Registrierungen zurück auf die Warteliste setzen."
#: members/admin.py:537
#: members/admin.py:555
msgid "Demote member to waiter"
msgstr "Ausgewählte Registrierung zurück auf die Warteliste setzen."
#: members/admin.py:546
#: members/admin.py:564
#, python-format
msgid "Successfully demoted %(name)s to waiter."
msgstr "%(name)s zurück auf die Warteliste gesetzt."
#: members/admin.py:561 members/models.py:437 members/models.py:840
#: members/admin.py:579 members/models.py:437 members/models.py:840
#: members/models.py:1329
msgid "Group"
msgstr "Gruppe"
#: members/admin.py:566
#: members/admin.py:584
msgid "Invitation text"
msgstr "Einladungstext"
#: members/admin.py:582
#: members/admin.py:600
msgid "Pending group invitation for group"
msgstr "Ausstehende Gruppeneinladung für Gruppe"
#: members/admin.py:615
#: members/admin.py:633
#, python-format
msgid "Successfully asked %(name)s to confirm their waiting status."
msgstr "Erfolgreich %(name)s aufgefordert den Wartelistenplatz zu bestätigen."
#: members/admin.py:616
#: members/admin.py:634
msgid "Ask selected waiters to confirm their waiting status"
msgstr "Wartende auffordern den Wartelistenplatz zu bestätigen"
#: members/admin.py:651 members/admin.py:681
#: members/admin.py:669
msgid "Offer waiter a place in a group."
msgstr "Personen auf der Warteliste einen Gruppenplatz anbieten."
#: members/admin.py:660 members/admin.py:698 members/admin.py:726
#: members/admin.py:686 members/admin.py:714
msgid ""
"An error occurred while trying to invite said members. Please try again."
msgstr ""
"Beim Einladen dieser Personen ist ein Fehler aufgetreten. Bitte versuche es "
"nochmal. "
#: members/admin.py:664 members/admin.py:703
#: members/admin.py:691
msgid ""
"The selected group does not have a contact email. Please first set a contact "
"email and then try again."
@ -217,39 +221,39 @@ msgstr ""
"Die ausgewählte Gruppe hat keine Kontakt E-Mail Adresse. Bitte stelle eine "
"Kontakt E-Mail Adresse ein und versuche es erneut."
#: members/admin.py:670 members/admin.py:731
#: members/admin.py:694 members/admin.py:728
msgid "Select group for invitation"
msgstr "Wähle Gruppe für Einladung aus"
#: members/admin.py:719
#, python-format
msgid "Successfully invited %(name)s to %(group)s."
msgstr "Erfolgreich %(name)s zu Gruppe %(group)s eingeladen."
#: members/admin.py:674 members/admin.py:706 members/admin.py:740
msgid "Select group for invitation"
msgstr "Wähle Gruppe für Einladung aus"
#: members/admin.py:760 members/models.py:72
#: members/admin.py:748 members/models.py:72
msgid "name"
msgstr "Name"
#: members/admin.py:761
#: members/admin.py:749
msgid ""
"The group name may only consist of letters, numerals, _, -, :, * and spaces."
msgstr ""
"Der Gruppenname darf nur aus Buchstaben, Zahlen, _, -, :, * oder Leerzeichen "
"bestehen."
#: members/admin.py:790
#: members/admin.py:778
msgid "Difficulty"
msgstr "Schwierigkeit"
#: members/admin.py:793
#: members/admin.py:781
msgid "Tour type"
msgstr "Art der Tour"
#: members/admin.py:796 members/models.py:1060
#: members/admin.py:784 members/models.py:1060
msgid "Means of transportation"
msgstr "Verkehrsmittel"
#: members/admin.py:823
#: members/admin.py:811
msgid ""
"Please list here all expenses in relation with this excursion and upload "
"relevant bills. These have to be permanently stored for the application of "
@ -262,7 +266,7 @@ msgstr ""
"einzelnen Posten wird dabei auf der LJP-Kostenübersicht angezeigt (sinnvoll "
"wären z.B. Anreise, Verpflegung, Material etc.)."
#: members/admin.py:841
#: members/admin.py:829
msgid ""
"Here you can work on a seminar report for applying for financial "
"contributions from Landesjugendplan (LJP). More information on creating a "
@ -275,7 +279,7 @@ msgstr ""
"wahlweise nur TN-Liste und Kostenübersicht kannst du anschließend "
"herunterladen."
#: members/admin.py:849
#: members/admin.py:837
msgid ""
"Please list all participants (also youth leaders) of this excursion. Here "
"you can still make changes just before departure and hence generate the "
@ -286,34 +290,34 @@ msgstr ""
"jederzeit die aktuelle Teilnehmer*innenliste für die Krisenintervention "
"generieren."
#: members/admin.py:895
#: members/admin.py:883
#, python-format
msgid "You are not allowed to view all members on note list %(name)s."
msgstr ""
"Du hast nicht die nötigen Rechte um alle Teilnehmer*innen der Notizliste "
"%(name)s anzusehen."
#: members/admin.py:905
#: members/admin.py:893
msgid "Generate PDF summary"
msgstr "Übersicht erstellen"
#: members/admin.py:909
#: members/admin.py:897
msgid "Full report"
msgstr "Vollständiger Seminarbericht"
#: members/admin.py:910
#: members/admin.py:898
msgid "Costs and participants only"
msgstr "Nur Kosten und Teilnehmende"
#: members/admin.py:911
#: members/admin.py:899
msgid "Mode"
msgstr "Modus"
#: members/admin.py:912
#: members/admin.py:900
msgid "Prepend V32"
msgstr "V32 Formblatt einfügen"
#: members/admin.py:928
#: members/admin.py:916
msgid ""
"General information on your excursion. These are partly relevant for the "
"amount of financial compensation (means of transport, travel distance, etc.)."
@ -322,48 +326,48 @@ msgstr ""
"teilweise relevant für die Zuschüsse aus dem Jugendetat (Verkehrsmittel, "
"Fahrstrecke in km)."
#: members/admin.py:958
#: members/admin.py:946
#, python-format
msgid "You are not allowed to view all members on excursion %(name)s."
msgstr ""
"Du hast nicht die nötigen Rechte um alle Teilnehmer*innen der Ausfahrt "
"%(name)s anzusehen."
#: members/admin.py:966
#: members/admin.py:954
msgid "Generate crisis intervention list"
msgstr "Kriseninterventionsliste erstellen"
#: members/admin.py:974
#: members/admin.py:962
msgid "Generate overview"
msgstr "Hinweise für Jugendleiter erstellen"
#: members/admin.py:978 members/admin.py:1010
#: members/admin.py:966 members/admin.py:998
#: members/templates/admin/generate_seminar_report.html:21
msgid "Generate seminar report"
msgstr "Landesjugendplan Antrag erstellen"
#: members/admin.py:991
#: members/admin.py:979
msgid "Please select a mode."
msgstr "Bitte wähle einen Modus aus."
#: members/admin.py:996
#: members/admin.py:984
msgid ""
"Full mode is only available, if the seminar report section is filled out."
msgstr ""
"Der vollständiger Modus ist nur verfügbar, wenn der Seminarbericht "
"ausgefüllt ist. "
#: members/admin.py:1022
#: members/admin.py:1010
msgid "Generate SJR application"
msgstr "SJR Antrag erstellen"
#: members/admin.py:1026
#: members/admin.py:1014
msgid "No statement found. Please add a statement and then retry."
msgstr ""
"Keine Abrechnung angelegt. Bitte lege eine Abrechnung and und versuche es "
"erneut."
#: members/admin.py:1030
#: members/admin.py:1018
msgid ""
"Successfully submited statement. The finance department will notify you as "
"soon as possible."
@ -371,7 +375,7 @@ msgstr ""
"Abrechnung erfolgreich eingericht. Die Finanzabteilung wird sich bei dir so "
"schnell wie möglich melden."
#: members/admin.py:1033
#: members/admin.py:1021
#: members/templates/admin/freizeit_finance_overview.html:21
msgid "Finance overview"
msgstr "Kostenübersicht"
@ -1316,8 +1320,8 @@ msgid ""
msgstr ""
"Der folgende Text wird in der Einladungsmail verschickt. Die Platzhalter "
"{name}, {link} und {invitation_reject_link} werden beim Senden automatisch "
"durch personalisierte Daten ersetzt. Bitte passe den Text falls nötig an "
"und schicke die Einladung anschließend ab."
"durch personalisierte Daten ersetzt. Bitte passe den Text falls nötig an und "
"schicke die Einladung anschließend ab."
#: members/templates/admin/invite_for_group_text.html:62
msgid "Send"

@ -0,0 +1,614 @@
/**
* @fileoverview
* - Using the 'QRCode for Javascript library'
* - Fixed dataset of 'QRCode for Javascript library' for support full-spec.
* - this library has no dependencies.
*
* @author davidshimjs
* @see <a href="http://www.d-project.com/" target="_blank">http://www.d-project.com/</a>
* @see <a href="http://jeromeetienne.github.com/jquery-qrcode/" target="_blank">http://jeromeetienne.github.com/jquery-qrcode/</a>
*/
var QRCode;
(function () {
//---------------------------------------------------------------------
// QRCode for JavaScript
//
// Copyright (c) 2009 Kazuhiko Arase
//
// URL: http://www.d-project.com/
//
// Licensed under the MIT license:
// http://www.opensource.org/licenses/mit-license.php
//
// The word "QR Code" is registered trademark of
// DENSO WAVE INCORPORATED
// http://www.denso-wave.com/qrcode/faqpatent-e.html
//
//---------------------------------------------------------------------
function QR8bitByte(data) {
this.mode = QRMode.MODE_8BIT_BYTE;
this.data = data;
this.parsedData = [];
// Added to support UTF-8 Characters
for (var i = 0, l = this.data.length; i < l; i++) {
var byteArray = [];
var code = this.data.charCodeAt(i);
if (code > 0x10000) {
byteArray[0] = 0xF0 | ((code & 0x1C0000) >>> 18);
byteArray[1] = 0x80 | ((code & 0x3F000) >>> 12);
byteArray[2] = 0x80 | ((code & 0xFC0) >>> 6);
byteArray[3] = 0x80 | (code & 0x3F);
} else if (code > 0x800) {
byteArray[0] = 0xE0 | ((code & 0xF000) >>> 12);
byteArray[1] = 0x80 | ((code & 0xFC0) >>> 6);
byteArray[2] = 0x80 | (code & 0x3F);
} else if (code > 0x80) {
byteArray[0] = 0xC0 | ((code & 0x7C0) >>> 6);
byteArray[1] = 0x80 | (code & 0x3F);
} else {
byteArray[0] = code;
}
this.parsedData.push(byteArray);
}
this.parsedData = Array.prototype.concat.apply([], this.parsedData);
if (this.parsedData.length != this.data.length) {
this.parsedData.unshift(191);
this.parsedData.unshift(187);
this.parsedData.unshift(239);
}
}
QR8bitByte.prototype = {
getLength: function (buffer) {
return this.parsedData.length;
},
write: function (buffer) {
for (var i = 0, l = this.parsedData.length; i < l; i++) {
buffer.put(this.parsedData[i], 8);
}
}
};
function QRCodeModel(typeNumber, errorCorrectLevel) {
this.typeNumber = typeNumber;
this.errorCorrectLevel = errorCorrectLevel;
this.modules = null;
this.moduleCount = 0;
this.dataCache = null;
this.dataList = [];
}
QRCodeModel.prototype={addData:function(data){var newData=new QR8bitByte(data);this.dataList.push(newData);this.dataCache=null;},isDark:function(row,col){if(row<0||this.moduleCount<=row||col<0||this.moduleCount<=col){throw new Error(row+","+col);}
return this.modules[row][col];},getModuleCount:function(){return this.moduleCount;},make:function(){this.makeImpl(false,this.getBestMaskPattern());},makeImpl:function(test,maskPattern){this.moduleCount=this.typeNumber*4+17;this.modules=new Array(this.moduleCount);for(var row=0;row<this.moduleCount;row++){this.modules[row]=new Array(this.moduleCount);for(var col=0;col<this.moduleCount;col++){this.modules[row][col]=null;}}
this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-7,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(test,maskPattern);if(this.typeNumber>=7){this.setupTypeNumber(test);}
if(this.dataCache==null){this.dataCache=QRCodeModel.createData(this.typeNumber,this.errorCorrectLevel,this.dataList);}
this.mapData(this.dataCache,maskPattern);},setupPositionProbePattern:function(row,col){for(var r=-1;r<=7;r++){if(row+r<=-1||this.moduleCount<=row+r)continue;for(var c=-1;c<=7;c++){if(col+c<=-1||this.moduleCount<=col+c)continue;if((0<=r&&r<=6&&(c==0||c==6))||(0<=c&&c<=6&&(r==0||r==6))||(2<=r&&r<=4&&2<=c&&c<=4)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}},getBestMaskPattern:function(){var minLostPoint=0;var pattern=0;for(var i=0;i<8;i++){this.makeImpl(true,i);var lostPoint=QRUtil.getLostPoint(this);if(i==0||minLostPoint>lostPoint){minLostPoint=lostPoint;pattern=i;}}
return pattern;},createMovieClip:function(target_mc,instance_name,depth){var qr_mc=target_mc.createEmptyMovieClip(instance_name,depth);var cs=1;this.make();for(var row=0;row<this.modules.length;row++){var y=row*cs;for(var col=0;col<this.modules[row].length;col++){var x=col*cs;var dark=this.modules[row][col];if(dark){qr_mc.beginFill(0,100);qr_mc.moveTo(x,y);qr_mc.lineTo(x+cs,y);qr_mc.lineTo(x+cs,y+cs);qr_mc.lineTo(x,y+cs);qr_mc.endFill();}}}
return qr_mc;},setupTimingPattern:function(){for(var r=8;r<this.moduleCount-8;r++){if(this.modules[r][6]!=null){continue;}
this.modules[r][6]=(r%2==0);}
for(var c=8;c<this.moduleCount-8;c++){if(this.modules[6][c]!=null){continue;}
this.modules[6][c]=(c%2==0);}},setupPositionAdjustPattern:function(){var pos=QRUtil.getPatternPosition(this.typeNumber);for(var i=0;i<pos.length;i++){for(var j=0;j<pos.length;j++){var row=pos[i];var col=pos[j];if(this.modules[row][col]!=null){continue;}
for(var r=-2;r<=2;r++){for(var c=-2;c<=2;c++){if(r==-2||r==2||c==-2||c==2||(r==0&&c==0)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}}}},setupTypeNumber:function(test){var bits=QRUtil.getBCHTypeNumber(this.typeNumber);for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[Math.floor(i/3)][i%3+this.moduleCount-8-3]=mod;}
for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[i%3+this.moduleCount-8-3][Math.floor(i/3)]=mod;}},setupTypeInfo:function(test,maskPattern){var data=(this.errorCorrectLevel<<3)|maskPattern;var bits=QRUtil.getBCHTypeInfo(data);for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<6){this.modules[i][8]=mod;}else if(i<8){this.modules[i+1][8]=mod;}else{this.modules[this.moduleCount-15+i][8]=mod;}}
for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<8){this.modules[8][this.moduleCount-i-1]=mod;}else if(i<9){this.modules[8][15-i-1+1]=mod;}else{this.modules[8][15-i-1]=mod;}}
this.modules[this.moduleCount-8][8]=(!test);},mapData:function(data,maskPattern){var inc=-1;var row=this.moduleCount-1;var bitIndex=7;var byteIndex=0;for(var col=this.moduleCount-1;col>0;col-=2){if(col==6)col--;while(true){for(var c=0;c<2;c++){if(this.modules[row][col-c]==null){var dark=false;if(byteIndex<data.length){dark=(((data[byteIndex]>>>bitIndex)&1)==1);}
var mask=QRUtil.getMask(maskPattern,row,col-c);if(mask){dark=!dark;}
this.modules[row][col-c]=dark;bitIndex--;if(bitIndex==-1){byteIndex++;bitIndex=7;}}}
row+=inc;if(row<0||this.moduleCount<=row){row-=inc;inc=-inc;break;}}}}};QRCodeModel.PAD0=0xEC;QRCodeModel.PAD1=0x11;QRCodeModel.createData=function(typeNumber,errorCorrectLevel,dataList){var rsBlocks=QRRSBlock.getRSBlocks(typeNumber,errorCorrectLevel);var buffer=new QRBitBuffer();for(var i=0;i<dataList.length;i++){var data=dataList[i];buffer.put(data.mode,4);buffer.put(data.getLength(),QRUtil.getLengthInBits(data.mode,typeNumber));data.write(buffer);}
var totalDataCount=0;for(var i=0;i<rsBlocks.length;i++){totalDataCount+=rsBlocks[i].dataCount;}
if(buffer.getLengthInBits()>totalDataCount*8){throw new Error("code length overflow. ("
+buffer.getLengthInBits()
+">"
+totalDataCount*8
+")");}
if(buffer.getLengthInBits()+4<=totalDataCount*8){buffer.put(0,4);}
while(buffer.getLengthInBits()%8!=0){buffer.putBit(false);}
while(true){if(buffer.getLengthInBits()>=totalDataCount*8){break;}
buffer.put(QRCodeModel.PAD0,8);if(buffer.getLengthInBits()>=totalDataCount*8){break;}
buffer.put(QRCodeModel.PAD1,8);}
return QRCodeModel.createBytes(buffer,rsBlocks);};QRCodeModel.createBytes=function(buffer,rsBlocks){var offset=0;var maxDcCount=0;var maxEcCount=0;var dcdata=new Array(rsBlocks.length);var ecdata=new Array(rsBlocks.length);for(var r=0;r<rsBlocks.length;r++){var dcCount=rsBlocks[r].dataCount;var ecCount=rsBlocks[r].totalCount-dcCount;maxDcCount=Math.max(maxDcCount,dcCount);maxEcCount=Math.max(maxEcCount,ecCount);dcdata[r]=new Array(dcCount);for(var i=0;i<dcdata[r].length;i++){dcdata[r][i]=0xff&buffer.buffer[i+offset];}
offset+=dcCount;var rsPoly=QRUtil.getErrorCorrectPolynomial(ecCount);var rawPoly=new QRPolynomial(dcdata[r],rsPoly.getLength()-1);var modPoly=rawPoly.mod(rsPoly);ecdata[r]=new Array(rsPoly.getLength()-1);for(var i=0;i<ecdata[r].length;i++){var modIndex=i+modPoly.getLength()-ecdata[r].length;ecdata[r][i]=(modIndex>=0)?modPoly.get(modIndex):0;}}
var totalCodeCount=0;for(var i=0;i<rsBlocks.length;i++){totalCodeCount+=rsBlocks[i].totalCount;}
var data=new Array(totalCodeCount);var index=0;for(var i=0;i<maxDcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<dcdata[r].length){data[index++]=dcdata[r][i];}}}
for(var i=0;i<maxEcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<ecdata[r].length){data[index++]=ecdata[r][i];}}}
return data;};var QRMode={MODE_NUMBER:1<<0,MODE_ALPHA_NUM:1<<1,MODE_8BIT_BYTE:1<<2,MODE_KANJI:1<<3};var QRErrorCorrectLevel={L:1,M:0,Q:3,H:2};var QRMaskPattern={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};var QRUtil={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:(1<<10)|(1<<8)|(1<<5)|(1<<4)|(1<<2)|(1<<1)|(1<<0),G18:(1<<12)|(1<<11)|(1<<10)|(1<<9)|(1<<8)|(1<<5)|(1<<2)|(1<<0),G15_MASK:(1<<14)|(1<<12)|(1<<10)|(1<<4)|(1<<1),getBCHTypeInfo:function(data){var d=data<<10;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)>=0){d^=(QRUtil.G15<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)));}
return((data<<10)|d)^QRUtil.G15_MASK;},getBCHTypeNumber:function(data){var d=data<<12;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)>=0){d^=(QRUtil.G18<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)));}
return(data<<12)|d;},getBCHDigit:function(data){var digit=0;while(data!=0){digit++;data>>>=1;}
return digit;},getPatternPosition:function(typeNumber){return QRUtil.PATTERN_POSITION_TABLE[typeNumber-1];},getMask:function(maskPattern,i,j){switch(maskPattern){case QRMaskPattern.PATTERN000:return(i+j)%2==0;case QRMaskPattern.PATTERN001:return i%2==0;case QRMaskPattern.PATTERN010:return j%3==0;case QRMaskPattern.PATTERN011:return(i+j)%3==0;case QRMaskPattern.PATTERN100:return(Math.floor(i/2)+Math.floor(j/3))%2==0;case QRMaskPattern.PATTERN101:return(i*j)%2+(i*j)%3==0;case QRMaskPattern.PATTERN110:return((i*j)%2+(i*j)%3)%2==0;case QRMaskPattern.PATTERN111:return((i*j)%3+(i+j)%2)%2==0;default:throw new Error("bad maskPattern:"+maskPattern);}},getErrorCorrectPolynomial:function(errorCorrectLength){var a=new QRPolynomial([1],0);for(var i=0;i<errorCorrectLength;i++){a=a.multiply(new QRPolynomial([1,QRMath.gexp(i)],0));}
return a;},getLengthInBits:function(mode,type){if(1<=type&&type<10){switch(mode){case QRMode.MODE_NUMBER:return 10;case QRMode.MODE_ALPHA_NUM:return 9;case QRMode.MODE_8BIT_BYTE:return 8;case QRMode.MODE_KANJI:return 8;default:throw new Error("mode:"+mode);}}else if(type<27){switch(mode){case QRMode.MODE_NUMBER:return 12;case QRMode.MODE_ALPHA_NUM:return 11;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 10;default:throw new Error("mode:"+mode);}}else if(type<41){switch(mode){case QRMode.MODE_NUMBER:return 14;case QRMode.MODE_ALPHA_NUM:return 13;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 12;default:throw new Error("mode:"+mode);}}else{throw new Error("type:"+type);}},getLostPoint:function(qrCode){var moduleCount=qrCode.getModuleCount();var lostPoint=0;for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount;col++){var sameCount=0;var dark=qrCode.isDark(row,col);for(var r=-1;r<=1;r++){if(row+r<0||moduleCount<=row+r){continue;}
for(var c=-1;c<=1;c++){if(col+c<0||moduleCount<=col+c){continue;}
if(r==0&&c==0){continue;}
if(dark==qrCode.isDark(row+r,col+c)){sameCount++;}}}
if(sameCount>5){lostPoint+=(3+sameCount-5);}}}
for(var row=0;row<moduleCount-1;row++){for(var col=0;col<moduleCount-1;col++){var count=0;if(qrCode.isDark(row,col))count++;if(qrCode.isDark(row+1,col))count++;if(qrCode.isDark(row,col+1))count++;if(qrCode.isDark(row+1,col+1))count++;if(count==0||count==4){lostPoint+=3;}}}
for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount-6;col++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row,col+1)&&qrCode.isDark(row,col+2)&&qrCode.isDark(row,col+3)&&qrCode.isDark(row,col+4)&&!qrCode.isDark(row,col+5)&&qrCode.isDark(row,col+6)){lostPoint+=40;}}}
for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount-6;row++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row+1,col)&&qrCode.isDark(row+2,col)&&qrCode.isDark(row+3,col)&&qrCode.isDark(row+4,col)&&!qrCode.isDark(row+5,col)&&qrCode.isDark(row+6,col)){lostPoint+=40;}}}
var darkCount=0;for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount;row++){if(qrCode.isDark(row,col)){darkCount++;}}}
var ratio=Math.abs(100*darkCount/moduleCount/moduleCount-50)/5;lostPoint+=ratio*10;return lostPoint;}};var QRMath={glog:function(n){if(n<1){throw new Error("glog("+n+")");}
return QRMath.LOG_TABLE[n];},gexp:function(n){while(n<0){n+=255;}
while(n>=256){n-=255;}
return QRMath.EXP_TABLE[n];},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)};for(var i=0;i<8;i++){QRMath.EXP_TABLE[i]=1<<i;}
for(var i=8;i<256;i++){QRMath.EXP_TABLE[i]=QRMath.EXP_TABLE[i-4]^QRMath.EXP_TABLE[i-5]^QRMath.EXP_TABLE[i-6]^QRMath.EXP_TABLE[i-8];}
for(var i=0;i<255;i++){QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]]=i;}
function QRPolynomial(num,shift){if(num.length==undefined){throw new Error(num.length+"/"+shift);}
var offset=0;while(offset<num.length&&num[offset]==0){offset++;}
this.num=new Array(num.length-offset+shift);for(var i=0;i<num.length-offset;i++){this.num[i]=num[i+offset];}}
QRPolynomial.prototype={get:function(index){return this.num[index];},getLength:function(){return this.num.length;},multiply:function(e){var num=new Array(this.getLength()+e.getLength()-1);for(var i=0;i<this.getLength();i++){for(var j=0;j<e.getLength();j++){num[i+j]^=QRMath.gexp(QRMath.glog(this.get(i))+QRMath.glog(e.get(j)));}}
return new QRPolynomial(num,0);},mod:function(e){if(this.getLength()-e.getLength()<0){return this;}
var ratio=QRMath.glog(this.get(0))-QRMath.glog(e.get(0));var num=new Array(this.getLength());for(var i=0;i<this.getLength();i++){num[i]=this.get(i);}
for(var i=0;i<e.getLength();i++){num[i]^=QRMath.gexp(QRMath.glog(e.get(i))+ratio);}
return new QRPolynomial(num,0).mod(e);}};function QRRSBlock(totalCount,dataCount){this.totalCount=totalCount;this.dataCount=dataCount;}
QRRSBlock.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];QRRSBlock.getRSBlocks=function(typeNumber,errorCorrectLevel){var rsBlock=QRRSBlock.getRsBlockTable(typeNumber,errorCorrectLevel);if(rsBlock==undefined){throw new Error("bad rs block @ typeNumber:"+typeNumber+"/errorCorrectLevel:"+errorCorrectLevel);}
var length=rsBlock.length/3;var list=[];for(var i=0;i<length;i++){var count=rsBlock[i*3+0];var totalCount=rsBlock[i*3+1];var dataCount=rsBlock[i*3+2];for(var j=0;j<count;j++){list.push(new QRRSBlock(totalCount,dataCount));}}
return list;};QRRSBlock.getRsBlockTable=function(typeNumber,errorCorrectLevel){switch(errorCorrectLevel){case QRErrorCorrectLevel.L:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+0];case QRErrorCorrectLevel.M:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+1];case QRErrorCorrectLevel.Q:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+2];case QRErrorCorrectLevel.H:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+3];default:return undefined;}};function QRBitBuffer(){this.buffer=[];this.length=0;}
QRBitBuffer.prototype={get:function(index){var bufIndex=Math.floor(index/8);return((this.buffer[bufIndex]>>>(7-index%8))&1)==1;},put:function(num,length){for(var i=0;i<length;i++){this.putBit(((num>>>(length-i-1))&1)==1);}},getLengthInBits:function(){return this.length;},putBit:function(bit){var bufIndex=Math.floor(this.length/8);if(this.buffer.length<=bufIndex){this.buffer.push(0);}
if(bit){this.buffer[bufIndex]|=(0x80>>>(this.length%8));}
this.length++;}};var QRCodeLimitLength=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];
function _isSupportCanvas() {
return typeof CanvasRenderingContext2D != "undefined";
}
// android 2.x doesn't support Data-URI spec
function _getAndroid() {
var android = false;
var sAgent = navigator.userAgent;
if (/android/i.test(sAgent)) { // android
android = true;
var aMat = sAgent.toString().match(/android ([0-9]\.[0-9])/i);
if (aMat && aMat[1]) {
android = parseFloat(aMat[1]);
}
}
return android;
}
var svgDrawer = (function() {
var Drawing = function (el, htOption) {
this._el = el;
this._htOption = htOption;
};
Drawing.prototype.draw = function (oQRCode) {
var _htOption = this._htOption;
var _el = this._el;
var nCount = oQRCode.getModuleCount();
var nWidth = Math.floor(_htOption.width / nCount);
var nHeight = Math.floor(_htOption.height / nCount);
this.clear();
function makeSVG(tag, attrs) {
var el = document.createElementNS('http://www.w3.org/2000/svg', tag);
for (var k in attrs)
if (attrs.hasOwnProperty(k)) el.setAttribute(k, attrs[k]);
return el;
}
var svg = makeSVG("svg" , {'viewBox': '0 0 ' + String(nCount) + " " + String(nCount), 'width': '100%', 'height': '100%', 'fill': _htOption.colorLight});
svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
_el.appendChild(svg);
svg.appendChild(makeSVG("rect", {"fill": _htOption.colorLight, "width": "100%", "height": "100%"}));
svg.appendChild(makeSVG("rect", {"fill": _htOption.colorDark, "width": "1", "height": "1", "id": "template"}));
for (var row = 0; row < nCount; row++) {
for (var col = 0; col < nCount; col++) {
if (oQRCode.isDark(row, col)) {
var child = makeSVG("use", {"x": String(col), "y": String(row)});
child.setAttributeNS("http://www.w3.org/1999/xlink", "href", "#template")
svg.appendChild(child);
}
}
}
};
Drawing.prototype.clear = function () {
while (this._el.hasChildNodes())
this._el.removeChild(this._el.lastChild);
};
return Drawing;
})();
var useSVG = document.documentElement.tagName.toLowerCase() === "svg";
// Drawing in DOM by using Table tag
var Drawing = useSVG ? svgDrawer : !_isSupportCanvas() ? (function () {
var Drawing = function (el, htOption) {
this._el = el;
this._htOption = htOption;
};
/**
* Draw the QRCode
*
* @param {QRCode} oQRCode
*/
Drawing.prototype.draw = function (oQRCode) {
var _htOption = this._htOption;
var _el = this._el;
var nCount = oQRCode.getModuleCount();
var nWidth = Math.floor(_htOption.width / nCount);
var nHeight = Math.floor(_htOption.height / nCount);
var aHTML = ['<table style="border:0;border-collapse:collapse;">'];
for (var row = 0; row < nCount; row++) {
aHTML.push('<tr>');
for (var col = 0; col < nCount; col++) {
aHTML.push('<td style="border:0;border-collapse:collapse;padding:0;margin:0;width:' + nWidth + 'px;height:' + nHeight + 'px;background-color:' + (oQRCode.isDark(row, col) ? _htOption.colorDark : _htOption.colorLight) + ';"></td>');
}
aHTML.push('</tr>');
}
aHTML.push('</table>');
_el.innerHTML = aHTML.join('');
// Fix the margin values as real size.
var elTable = _el.childNodes[0];
var nLeftMarginTable = (_htOption.width - elTable.offsetWidth) / 2;
var nTopMarginTable = (_htOption.height - elTable.offsetHeight) / 2;
if (nLeftMarginTable > 0 && nTopMarginTable > 0) {
elTable.style.margin = nTopMarginTable + "px " + nLeftMarginTable + "px";
}
};
/**
* Clear the QRCode
*/
Drawing.prototype.clear = function () {
this._el.innerHTML = '';
};
return Drawing;
})() : (function () { // Drawing in Canvas
function _onMakeImage() {
this._elImage.src = this._elCanvas.toDataURL("image/png");
this._elImage.style.display = "block";
this._elCanvas.style.display = "none";
}
// Android 2.1 bug workaround
// http://code.google.com/p/android/issues/detail?id=5141
if (this._android && this._android <= 2.1) {
var factor = 1 / window.devicePixelRatio;
var drawImage = CanvasRenderingContext2D.prototype.drawImage;
CanvasRenderingContext2D.prototype.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) {
if (("nodeName" in image) && /img/i.test(image.nodeName)) {
for (var i = arguments.length - 1; i >= 1; i--) {
arguments[i] = arguments[i] * factor;
}
} else if (typeof dw == "undefined") {
arguments[1] *= factor;
arguments[2] *= factor;
arguments[3] *= factor;
arguments[4] *= factor;
}
drawImage.apply(this, arguments);
};
}
/**
* Check whether the user's browser supports Data URI or not
*
* @private
* @param {Function} fSuccess Occurs if it supports Data URI
* @param {Function} fFail Occurs if it doesn't support Data URI
*/
function _safeSetDataURI(fSuccess, fFail) {
var self = this;
self._fFail = fFail;
self._fSuccess = fSuccess;
// Check it just once
if (self._bSupportDataURI === null) {
var el = document.createElement("img");
var fOnError = function() {
self._bSupportDataURI = false;
if (self._fFail) {
self._fFail.call(self);
}
};
var fOnSuccess = function() {
self._bSupportDataURI = true;
if (self._fSuccess) {
self._fSuccess.call(self);
}
};
el.onabort = fOnError;
el.onerror = fOnError;
el.onload = fOnSuccess;
el.src = "data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; // the Image contains 1px data.
return;
} else if (self._bSupportDataURI === true && self._fSuccess) {
self._fSuccess.call(self);
} else if (self._bSupportDataURI === false && self._fFail) {
self._fFail.call(self);
}
};
/**
* Drawing QRCode by using canvas
*
* @constructor
* @param {HTMLElement} el
* @param {Object} htOption QRCode Options
*/
var Drawing = function (el, htOption) {
this._bIsPainted = false;
this._android = _getAndroid();
this._htOption = htOption;
this._elCanvas = document.createElement("canvas");
this._elCanvas.width = htOption.width;
this._elCanvas.height = htOption.height;
el.appendChild(this._elCanvas);
this._el = el;
this._oContext = this._elCanvas.getContext("2d");
this._bIsPainted = false;
this._elImage = document.createElement("img");
this._elImage.alt = "Scan me!";
this._elImage.style.display = "none";
this._el.appendChild(this._elImage);
this._bSupportDataURI = null;
};
/**
* Draw the QRCode
*
* @param {QRCode} oQRCode
*/
Drawing.prototype.draw = function (oQRCode) {
var _elImage = this._elImage;
var _oContext = this._oContext;
var _htOption = this._htOption;
var nCount = oQRCode.getModuleCount();
var nWidth = _htOption.width / nCount;
var nHeight = _htOption.height / nCount;
var nRoundedWidth = Math.round(nWidth);
var nRoundedHeight = Math.round(nHeight);
_elImage.style.display = "none";
this.clear();
for (var row = 0; row < nCount; row++) {
for (var col = 0; col < nCount; col++) {
var bIsDark = oQRCode.isDark(row, col);
var nLeft = col * nWidth;
var nTop = row * nHeight;
_oContext.strokeStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;
_oContext.lineWidth = 1;
_oContext.fillStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;
_oContext.fillRect(nLeft, nTop, nWidth, nHeight);
// 안티 앨리어싱 방지 처리
_oContext.strokeRect(
Math.floor(nLeft) + 0.5,
Math.floor(nTop) + 0.5,
nRoundedWidth,
nRoundedHeight
);
_oContext.strokeRect(
Math.ceil(nLeft) - 0.5,
Math.ceil(nTop) - 0.5,
nRoundedWidth,
nRoundedHeight
);
}
}
this._bIsPainted = true;
};
/**
* Make the image from Canvas if the browser supports Data URI.
*/
Drawing.prototype.makeImage = function () {
if (this._bIsPainted) {
_safeSetDataURI.call(this, _onMakeImage);
}
};
/**
* Return whether the QRCode is painted or not
*
* @return {Boolean}
*/
Drawing.prototype.isPainted = function () {
return this._bIsPainted;
};
/**
* Clear the QRCode
*/
Drawing.prototype.clear = function () {
this._oContext.clearRect(0, 0, this._elCanvas.width, this._elCanvas.height);
this._bIsPainted = false;
};
/**
* @private
* @param {Number} nNumber
*/
Drawing.prototype.round = function (nNumber) {
if (!nNumber) {
return nNumber;
}
return Math.floor(nNumber * 1000) / 1000;
};
return Drawing;
})();
/**
* Get the type by string length
*
* @private
* @param {String} sText
* @param {Number} nCorrectLevel
* @return {Number} type
*/
function _getTypeNumber(sText, nCorrectLevel) {
var nType = 1;
var length = _getUTF8Length(sText);
for (var i = 0, len = QRCodeLimitLength.length; i <= len; i++) {
var nLimit = 0;
switch (nCorrectLevel) {
case QRErrorCorrectLevel.L :
nLimit = QRCodeLimitLength[i][0];
break;
case QRErrorCorrectLevel.M :
nLimit = QRCodeLimitLength[i][1];
break;
case QRErrorCorrectLevel.Q :
nLimit = QRCodeLimitLength[i][2];
break;
case QRErrorCorrectLevel.H :
nLimit = QRCodeLimitLength[i][3];
break;
}
if (length <= nLimit) {
break;
} else {
nType++;
}
}
if (nType > QRCodeLimitLength.length) {
throw new Error("Too long data");
}
return nType;
}
function _getUTF8Length(sText) {
var replacedText = encodeURI(sText).toString().replace(/\%[0-9a-fA-F]{2}/g, 'a');
return replacedText.length + (replacedText.length != sText ? 3 : 0);
}
/**
* @class QRCode
* @constructor
* @example
* new QRCode(document.getElementById("test"), "http://jindo.dev.naver.com/collie");
*
* @example
* var oQRCode = new QRCode("test", {
* text : "http://naver.com",
* width : 128,
* height : 128
* });
*
* oQRCode.clear(); // Clear the QRCode.
* oQRCode.makeCode("http://map.naver.com"); // Re-create the QRCode.
*
* @param {HTMLElement|String} el target element or 'id' attribute of element.
* @param {Object|String} vOption
* @param {String} vOption.text QRCode link data
* @param {Number} [vOption.width=256]
* @param {Number} [vOption.height=256]
* @param {String} [vOption.colorDark="#000000"]
* @param {String} [vOption.colorLight="#ffffff"]
* @param {QRCode.CorrectLevel} [vOption.correctLevel=QRCode.CorrectLevel.H] [L|M|Q|H]
*/
QRCode = function (el, vOption) {
this._htOption = {
width : 256,
height : 256,
typeNumber : 4,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRErrorCorrectLevel.H
};
if (typeof vOption === 'string') {
vOption = {
text : vOption
};
}
// Overwrites options
if (vOption) {
for (var i in vOption) {
this._htOption[i] = vOption[i];
}
}
if (typeof el == "string") {
el = document.getElementById(el);
}
if (this._htOption.useSVG) {
Drawing = svgDrawer;
}
this._android = _getAndroid();
this._el = el;
this._oQRCode = null;
this._oDrawing = new Drawing(this._el, this._htOption);
if (this._htOption.text) {
this.makeCode(this._htOption.text);
}
};
/**
* Make the QRCode
*
* @param {String} sText link data
*/
QRCode.prototype.makeCode = function (sText) {
this._oQRCode = new QRCodeModel(_getTypeNumber(sText, this._htOption.correctLevel), this._htOption.correctLevel);
this._oQRCode.addData(sText);
this._oQRCode.make();
this._el.title = sText;
this._oDrawing.draw(this._oQRCode);
this.makeImage();
};
/**
* Make the Image from Canvas element
* - It occurs automatically
* - Android below 3 doesn't support Data-URI spec.
*
* @private
*/
QRCode.prototype.makeImage = function () {
if (typeof this._oDrawing.makeImage == "function" && (!this._android || this._android >= 3)) {
this._oDrawing.makeImage();
}
};
/**
* Clear the QRCode
*/
QRCode.prototype.clear = function () {
this._oDrawing.clear();
};
/**
* @name QRCode.CorrectLevel
*/
QRCode.CorrectLevel = QRErrorCorrectLevel;
})();

@ -48,6 +48,7 @@ pytz==2021.3
redis==4.1.0
requests==2.32.3
rules==3.3
schwifty==2024.11.0
six==1.16.0
snowballstemmer==2.2.0
Sphinx==7.4.7

Loading…
Cancel
Save