You may know about GNU Taler (https://taler.net/), and we would like to see GNU Taler integrated with the (Free Software) ticket sales system Pretix (https://pretix.com/). Pretix is written (mostly) in Python, and doing so will also require some system administration (like setting up the software yourself).
CLOSED: [2020-07-29 mié 19:12]
To chat on the internet
Mumble is an open source low-latency VoIP (Voice over IP) chat software primarily intended for use while gaming. It is distributed under the New BSD License. Users can use Mumble to connect to the public server or set up their own server to connect to with Murmur, allowing one to communicate with multiple parties at once by voice-over chat. Mumble connects to a server via a TLS/TCP control channel. While it normally uses a UDP audio channel, using Mumble over Tor forces traffic entirely over the control channel, and uses the ciphers that it supports. This is due to Tors inability to handle UDP packets.
Mumble is end-to-server encrypted with AES in OCB mode to increase user privacy. OCB is used to provide both secrecy and authentication while maintaining low latency.
In 1.2.0 Mumble implemented certificate authentication, eliminating the need for passwords and providing a significantly more secure form of verification. Many servers now require such certificate authentication to connect.
As of 1.2.9, Mumble also supports forward secrecy.
client portion of the software (Mumble) and the server portion (Murmur).
pacman -S murmur mumble
CLOSED: [2020-07-29 mié 19:31] :LOGBOOK: CLOCK: [2020-07-29 mié 19:15]--[2020-07-29 mié 19:31] => 0:16 :END:
To share code
We can also setup a git at git.taler.net for you. But, notabug.org is also fine, as long as we agree that the code must be Free Software under a Pretix-compatible license. Pretix itself is under Apache License v2, but LGPL or GPLv3 should also be acceptable (and for GNU preferred ;-)).
CLOSED: [2020-07-29 mié 21:41] :LOGBOOK: CLOCK: [2020-07-30 jue 09:41]--[2020-07-30 jue 10:06] => 0:25 CLOCK: [2020-07-29 mié 20:43]--[2020-07-29 mié 21:41] => 0:58 CLOCK: [2020-07-29 mié 19:35]--[2020-07-29 mié 20:03] => 0:28 :END:
To document code
We use 'rst' (Sphinx) for documentation, see https://git.taler.net/docs.git -- it would be nice if you could also produce a ".rst" file, say something like a "pretix-taler.rst", just to keep in line with our standards.
# Create the `Doc' dir if needed ! -d "Doc" && mkdir Doc # Create a barebones sphinx (Makefile, etc.) cd Doc && sphinx-quickstart
cat << EOF > index.rst
.. Taprelix documentation master file, created by
sphinx-quickstart on Wed Jul 29 20:56:34 2020.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to Taprelix's documentation!
====================================
.. toctree::
:maxdepth: 2
:caption: Contents:
EOF
#+end_SRC
*** Download exporting engine of reStructuredText for Org
#+begin_SRC bash
cd ~/.emacs.d/plugins/
torsocks git clone https://github.com/msnoigrs/ox-rst.git
cat << EOF >> ~/.emacs
(use-package ox-rst
:load-path "~/.emacs.d/plugins/ox-rst/")
#+end_SRC
*** Test
:EXPORT_FILE_NAME: Doc/src/test.rst
:END:
This is a test. Export this subheading, then do the following in [[file:Doc]]:
#+name: blk-add-rst-to-sphinx-index.sh
#+begin_SRC bash :exports code :results none :dir Doc/src :noweb yes :var file="test.rst"
<<blk-create-index.rst.sh>>
sed -i 's%^\( \+\)\(.*Contents:\)%\1\2\n\n\1'"$file"'%g' index.rst
cd ..
make html
#+end_SRC
*** Change exporting directory
#+caption: From [[https://stackoverflow.com/a/47850858][StackOverflow]]
#+begin_SRC emacs-lisp :results none
(defun org-export-output-file-name-modified (orig-fun extension &optional subtreep pub-dir)
(unless pub-dir
(setq pub-dir "./Doc/src")
(unless (file-directory-p pub-dir)
(make-directory pub-dir)))
(apply orig-fun extension subtreep pub-dir nil))
(advice-add 'org-export-output-file-name :around #'org-export-output-file-name-modified)
#+end_SRC
*** Add exported file to index.rst
#+call: blk-add-rst-to-sphinx-index.sh(file='taler-pretix-interface.rst')
** DONE take a look at the Taler Merchant backend API
CLOSED: [2020-08-02 dom 17:09]
:LOGBOOK:
CLOCK: [2020-07-30 jue 12:25]--[2020-07-30 jue 13:30] => 1:05
CLOCK: [2020-07-30 jue 09:20]--[2020-07-30 jue 09:38] => 0:18
:END:
(chapter 1.5 at https://docs.taler.net/)
*** Clone documentation
#+begin_SRC bash
git submodule add --name docs -- "git://git.taler.net/docs.git" docs
git submodule add --name website --force -- "git://git.taler.net/www.git" www
#+end_SRC
*** [[file:docs/taler-merchant-manual.rst::who want to install][Install]] in Parabola GNU/Linux
:LOGBOOK:
CLOCK: [2020-08-01 sáb 11:45]--[2020-08-01 sáb 12:39] => 0:54
CLOCK: [2020-07-30 jue 21:31]--[2020-07-30 jue 22:00] => 0:29
CLOCK: [2020-07-30 jue 17:51]--[2020-07-30 jue 19:55] => 2:04
CLOCK: [2020-07-30 jue 15:53]--[2020-07-30 jue 17:30] => 1:58
:END:
#+caption: PKGBUILD for gnunet with git
#+begin_SRC pkgbuild
# Maintainer: Sergej Pupykin <pupykin.s+arch@gmail.com>
# Contributor: wahnby <wahnby@yahoo.fr>
# Modified by eDgar
pkgname='gnunet'
pkgver=''
pkgrel=1
pkgdesc="A framework for secure peer-to-peer networking"
arch=('x86_64')
url="https://gnunet.org"
license=('GPL')
depends=('libextractor' 'sqlite' 'curl' 'libmicrohttpd' 'libidn' 'jansson' 'libsodium')
makedepends=('git' 'bluez-libs' 'python' 'glpk' 'libpulse' 'opus' 'gst-plugins-base-libs')
optdepends=('bluez-libs'
'python'
'glpk'
'libpulse'
'opus'
'gst-plugins-base-libs: for gnunet-helper-audio-record')
backup=(etc/gnunetd.conf)
options=('!makeflags')
# TODO: key revoked, check for new signature on new release
# source=(https://ftp.gnu.org/gnu/gnunet/gnunet-${pkgver/_/}.tar.gz{,.sig}
#source=(https://ftp.gnu.org/gnu/gnunet/gnunet-${pkgver/_/}.tar.gz
source=("gnunet.openrc"
"git+https://gnunet.org/git/${pkgname}.git"
'gnunet.service'
'defaults.conf')
md5sums=("1b2bf93b0b81f3eabd3ace4f3f4efc13"
'SKIP'
'a64f19ce71c02c200fa78ca2d1585bc8'
'0fe23b2ca5b3fc47a0b5645e04406da0')
pkgver() {
cd "${pkgname}"
printf "'%s.r%s.%s'" \
"$(grep 'AC_INIT' configure.ac | grep -o '[0-9]\(\.[0-9]\+\)\+')" \
"$(git rev-list --count HEAD)" \
"$(git rev-parse --short HEAD)"
}
prepare() {
cd "${pkgname}"
./bootstrap
}
build() {
cd "${pkgname}"
[ -f Makefile ] || ./configure --prefix=/usr \
--without-mysql
make
make -C contrib
}
package() {
cd gnunet
make DESTDIR="$pkgdir" install
make DESTDIR="$pkgdir" -C contrib install
install -D -m0644 "$srcdir"/defaults.conf "$pkgdir"/etc/gnunetd.conf
rm -rf "$pkgdir"/usr/include/libltdl "$pkgdir"/usr/lib/libltdl.* "$pkgdir"/usr/include/ltdl.h
install -Dm0644 "$srcdir"/$pkgname.service "$pkgdir"/usr/lib/systemd/system/$pkgname.service
install -D -m755 "$srcdir/${pkgname}.openrc" "$pkgdir/etc/init.d/${pkgname}d"
}
#+end_SRC
#+caption: OpenRC service for gnunet
#+begin_SRC conf :file /etc/init.d/gnunetd :tangle "media/gnunetd"
#!/usr/bin/openrc-run
# /etc/init.d/gnunetd
# Distributed under the terms of the GNU General Public License v3
# opts="start stop restart"
name="GNUnet Service Daemon"
command=/usr/bin/gnunet-arm
pidfile="/run/${RC_SVCNAME}.pid"
# Tell OpenRC to send gnunet to go into background and
# create a pid file (gnunet does not do it automatically)
command_background=true
# Use this user and group (create before hand)
command_user="gnunet:gnunet"
start() {
ebegin "Starting GNUnet"
"$command" -s -c /etc/gnunetd.conf
eend 0
}
stop() {
ebegin "Stopping GNUnet"
"$command" -e -c /etc/gnunetd.conf
eend 0
}
#+end_SRC
#+caption: PKGBUILD for taler-exchange with git
#+begin_SRC pkgbuild :noweb yes
# Based on the gnunet PKGBUILD from Arch Linux
pkgname='taler-exchange'
pkgver='0.7.0.r4298.79982222'
pkgrel=1
pkgdesc="Taler-specific exchange to manage financial transactions"
arch=('x86_64')
url="https://taler.org"
license=('GPL')
depends=('libextractor' 'sqlite' 'curl' 'libmicrohttpd' 'libidn' 'jansson' 'libsodium' 'gnunet')
makedepends=('git' 'bluez-libs' 'python' 'glpk' 'libpulse' 'opus' 'gst-plugins-base-libs' 'recutils')
options=('!makeflags')
source=("${pkgname}::git+https://git.taler.net/exchange.git")
md5sums=('SKIP')
pkgver() {
cd "${pkgname}"
printf "'%s.r%s.%s'" \
"$(grep 'AC_INIT' configure.ac | grep -o '[0-9]\(\.[0-9]\+\)\+')" \
"$(git rev-list --count HEAD)" \
"$(git rev-parse --short HEAD)"
}
prepare() {
cd ${pkgname}
# I don't think that this is very acceptable when building
# a package (download more external sources)
./bootstrap
<<blk-replace-include-taler.sh>>
}
build() {
cd ${pkgname}
[ -f Makefile ] || ./configure --prefix=/usr \
--without-mysql
make clean
make
make -C contrib
}
package() {
cd "${pkgname}"
make DESTDIR="$pkgdir" install
make DESTDIR="$pkgdir" -C contrib install
# install -D -m0644 "$srcdir"/defaults.conf "$pkgdir"/etc/talerd.conf
# rm -rf "$pkgdir"/usr/include/libltdl "$pkgdir"/usr/lib/libltdl.* "$pkgdir"/usr/include/ltdl.h
# install -Dm0644 "$srcdir"/$pkgname.service "$pkgdir"/usr/lib/systemd/system/$pkgname.service
}
#+end_SRC
#+caption: PKGBUILD for taler-merchant-backend with git
#+begin_SRC pkgbuild
# Based on the gnunet PKGBUILD from Arch Linux
pkgname='taler-merchant-backend'
pkgver='0.7.0.r2514.859675fe'
pkgrel=1
pkgdesc="Taler-specific payment backend to process financial transactions"
arch=('x86_64')
url="https://taler.org"
license=('GPL')
depends=('libextractor' 'sqlite' 'curl' 'libmicrohttpd' 'libidn' 'jansson' 'libsodium' 'gnunet' 'taler-exchange')
makedepends=('git' 'bluez-libs' 'python' 'glpk' 'libpulse' 'opus' 'gst-plugins-base-libs' 'recutils')
options=('!makeflags')
source=("${pkgname}::git+https://git.taler.net/merchant.git"
"${pkgname}.openrc")
md5sums=('SKIP'
'3bb700f66f781f527edd5903fe4894b2')
pkgver() {
cd "${pkgname}"
printf "'%s.r%s.%s'" \
"$(grep 'AC_INIT' configure.ac | grep -o '[0-9]\(\.[0-9]\+\)\+')" \
"$(git rev-list --count HEAD)" \
"$(git rev-parse --short HEAD)"
}
prepare() {
cd ${pkgname}
# Make sure that this script is benign
./bootstrap
}
build() {
cd ${pkgname}
[ -f Makefile ] || ./configure --prefix=/usr \
--without-mysql
make clean
make
make -C contrib
}
package() {
cd "${pkgname}"
make DESTDIR="$pkgdir" install
make DESTDIR="$pkgdir" -C contrib install
install -D -m755 "$srcdir/${pkgname}.openrc" "$pkgdir/etc/init.d/${pkgname}"
# install -D -m0644 "$srcdir"/defaults.conf "$pkgdir"/etc/talerd.conf
# rm -rf "$pkgdir"/usr/include/libltdl "$pkgdir"/usr/lib/libltdl.* "$pkgdir"/usr/include/ltdl.h
# install -Dm0644 "$srcdir"/$pkgname.service "$pkgdir"/usr/lib/systemd/system/$pkgname.service
}
#+end_SRC
#+caption: OpenRC service for taler-merchant-httpd (merchant backend).
#+begin_SRC conf :tangle "/media/taler-merchant-backend"
#!/usr/bin/openrc-run
# Distributed under the terms of the GNU General Public License v3
# opts="start stop restart"
name="Taler Service Daemon"
command=/usr/bin/taler-merchant-httpd
command_args="-c /var/lib/postgres/.config/taler.conf -l /var/lib/postgres/taler.log"
pidfile="/run/${RC_SVCNAME}.pid"
# Tell OpenRC to send the command into the background and
# create a pid file (the command does not do it automatically)
command_background=true
command_user=postgres
#+end_SRC
*** Configure
:LOGBOOK:
CLOCK: [2020-09-12 sáb 12:35]--[2020-09-12 sáb 13:43] => 1:08
CLOCK: [2020-09-12 sáb 12:19]--[2020-09-12 sáb 12:29] => 0:10
CLOCK: [2020-08-01 sáb 14:39]--[2020-08-01 sáb 15:00] => 0:21
CLOCK: [2020-08-01 sáb 13:34]--[2020-08-01 sáb 14:16] => 0:42
CLOCK: [2020-08-01 sáb 12:40]--[2020-08-01 sáb 13:00] => 0:20
:END:
I had to do this to be able to use the backend
#+begin_QUOTE
You do not need to setup your own Taler payment backend, there is a setup at https://backend.test.taler.net/. The required HTTP "Authorization" header is "ApiKey sandbox".
--Christian Grothoff
#+end_QUOTE
#+caption: From [[file:docs/taler-nfc-guide.rst][/taler-nfc-guide.rst/]]
#+begin_SRC sh
backend_base_url=https://backend.demo.taler.net/
auth_header='Authorization: ApiKey sandbox'
order_req=$(cat <<EOF
{
"order": {
"summary": "one ice cream",
"amount": "KUDOS:1.5",
"fulfillment_url":
"taler://fulfillment-success/Enjoy+your+ice+cream!"
}
}
EOF
)
curl -XPOST -H"$auth_header" -d "$order_req" "$backend_base_url"/private/orders
{
"order_id": "2019.255-02YDHMXCBQP6J"
}
#+end_SRC
Therefore, use =Authorization: ApiKey sandbox= in this:
#+begin_QUOTE
curl -H "Content-Type: application/json" -X POST -d '{"payto_uris":["payto://x-taler-bank/bank.test.taler.net/lux"],"id":"lux","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1","default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_ms": 3600000},"default_pay_delay":{"d_ms": 3600000}}' http://backend.test.taler.net/private/instances
#+end_QUOTE
#+name: blk-calling-apikey-sandbox.sh
#+caption: Calling the web API with Apikey sandbox authorization
#+begin_SRC sh :results raw
# https://dev.to/ama/curl-commands-examples-to-make-rest-api-calls-4gg3
curl -v \
-H "Content-Type: application/json"\
-H "Authorization: ApiKey sandbox" \
-X POST \
-d '{"payto_uris":["payto://x-taler-bank/bank.test.taler.net/lux"],"id":"lux","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1","default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_ms": 3600000},"default_pay_delay":{"d_ms": 3600000}}' \
http://backend.test.taler.net/private/instances
#+end_SRC
#+RESULTS:
#+caption: The response is empty, but the /lux/ instance is created ([[#46eadf9c-ce89-41c5-af1a-85703c78078b][thanks, Christian]])
#+begin_EXAMPLE
Note: Unnecessary use of -X or --request, POST is already inferred.
,* Trying 147.87.255.221:80...
,* Connected to backend.test.taler.net (147.87.255.221) port 80 (#0)
> POST /private/instances HTTP/1.1
> Host: backend.test.taler.net
> User-Agent: curl/7.71.1
> Accept: */*
> Content-Type: application/json,
> Authorization: ApiKey sandbox
> Content-Length: 319
>
,* upload completely sent off: 319 out of 319 bytes
,* Mark bundle as not supporting multiuse
< HTTP/1.1 204 No Content
< Server: nginx
< Date: Sat, 01 Aug 2020 19:03:51 GMT
< Connection: keep-alive
< Access-Control-Allow-Origin: *
< Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
< X-XSS-Protection: 1; mode=block
< X-Frame-Options: SAMEORIGIN
< X-Content-Type-Options: nosniff
< Content-Security-Policy: default-src 'self' https://www.taler.net/dist/; img-src 'self' https://secure.gravatar.com/ https://git.taler.net/ data: blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.taler.net/dist/js/; style-src 'self' 'unsafe-inline' https://www.taler.net/dist/css/; connect-src 'self' wss://buildbot.taler.net; object-src 'self' blob:
< Referrer-Policy: same-origin
<
,* Connection #0 to host backend.test.taler.net left intact
#+end_EXAMPLE
When I call
#+begin_SRC sh
taler-config -s MERCHANT -o PORT -V 8888
#+end_SRC
I get a file =~/.config/taler.conf= with almost everything preset! (this is not the file that needs to be used).
According to [[file:docs/taler-merchant-manual.rst::createuser -d][taler-merchant-manual.rst]], I can use PostgreSQL. So I create the user[fn::I already have PostgreSQL installed and configured in my system. Check https://notabug.org/broncodev/tryton-setup/src/master/notes.org for a way to do it]
#+caption: Create user for taler in PostgreSQL server
#+begin_SRC bash :var USER="taler"
sudo -u postgres createuser -d $USER
#+end_SRC
Note that the following code will create a configuration file in the default home directory of the =postgres= user. In my case, this was =/var/lib/postgres/.config=. The =-H= argument to =sudo= should change the working directory to the user's home, but it does not always behave as expected. You may need to change to the directory first.
#+CAPTION: Create the configuration file for taler in the user directory of =postgres= (the main PostgreSQL user, the one which will run the =taler-merchant-httpd= command or service)
#+begin_src bash :dir "/sudo::/var/lib/postgres" :cache no
sudo -H -u postgres taler-config -s MERCHANT -o SERVE -V TCP
#+end_src
#+name: blk-merchantdb-postgres-config-val.sh
#+caption: Set database name in configuration (and create default =~/.config/taler.conf=)
#+begin_SRC bash :dir "/sudo::/var/lib/postgres" :cache no :var DBNAME="talermerchant" taler_user="taler"
sudo -H -u postgres taler-config -s MERCHANTDB-postgres -o CONFIG -V "postgres://${taler_user}:@/$DBNAME"
#+end_SRC
#+RESULTS: blk-merchantdb-postgres-config-val.sh
#+caption: Create PostgreSQL database for taler
#+begin_SRC bash :var taler_user="taler" DBNAME="talermerchant" :dir "/sudo::" :cache no
sudo -u postgres createdb --owner=${taler_user} --encoding=utf-8 $DBNAME
#+end_SRC
#+begin_QUOTE
You can improve your security posture if you now REVOKE the rights to CREATE, DROP or ALTER tables from ``$USER``. However, if you do so, please be aware that you may have to temporarily GRANT those rights again when you update the merchant backend.
#+end_QUOTE
#+caption: Access PostgreSQL command line
#+begin_SRC bash :dir "/sudo::" :cache no
sudo -u postgres psql
#+end_SRC
#+caption: Revoke privileges for the Taler user once inside =psql=
#+begin_SRC sql
REVOKE CREATE, DROP, ALTER ON SCHEMA public from taler;
quit;
#+end_SRC
#+caption: remove privileges from Taler user in one go.
#+begin_SRC bash :dir "/sudo::" :cache no :var taler_user="taler"
sudo -u postgres psql -c "REVOKE CREATE, DROP, ALTER ON SCHEMA public from ${taler_user};"
#+end_SRC
#+name: blk-configure-backend-local-socket.sh
#+caption: Configure local socket (from [[file:docs/taler-merchant-manual.rst::.. index:: TCP][docs]])
#+begin_SRC bash :dir "/sudo::/var/lib/postgres" :cache no
sudo -H -u postgres taler-config -s MERCHANT -o SERVE -V TCP
sudo -H -u postgres taler-config -s MERCHANT -o PORT -V 8888
#+end_SRC
#+CAPTION: Test if Taler is running
#+begin_src bash :dir "/sudo::/var/lib/postgres" :cache no
sudo -u postgres taler-merchant-httpd -c /var/lib/postgres/.config/taler.conf -l /tmp/taler.log -L DEBUG
#+end_src
#+begin_src bash :dir "/sudo::/var/lib/postgres" :cache no
cat /tmp/taler.log
#+end_src
#+RESULTS:
: Sep 12 13:11:45-056466 taler-merchant-httpd-13398 INFO Starting taler-merchant-httpd
#+begin_src bash :dir "/sudo::/var/lib/postgres" :cache no
sudo rc-service taler-merchant-backend start
#+end_src
#+RESULTS:
: * Starting Taler Service Daemon ... [ ok ]
#+CAPTION: OpenRC service to load all the requirements of Pretix and Taler
#+begin_src conf :file /etc/init.d/taler-server :tangle "media/taler-server" :tangle-mode 755
#!/usr/bin/openrc-run
# Distributed under the terms of the GNU General Public License v3
# /etc/init.d/taler-server
# opts="start stop restart"
name="Services for Taler"
pidfile="/run/${RC_SVCNAME}.pid"
depends() {
provide pretix-load taler-merchant-backend;
}
start_pre () {
rc-service pretix-load start
}
start () {
rc-service taler-merchant-backend start
}
stop () {
rc-service taler-merchant-backend stop
}
stop_post () {
rc-service pretix-load stop
}
#+end_src
**** Prompt for local backend
#+begin_SRC bash
curl --no-progress-meter http://localhost:8888/ 2>&1
#+end_SRC
#+RESULTS:
: This is a GNU Taler merchant backend. See https://taler.net/.
#+name: blk-taler-prompt-local-backend.py
#+begin_SRC python :results output replace
import requests
reqres = requests.get("http://localhost:8888/private/instances")
print(reqres)
#+end_SRC
#+RESULTS: blk-taler-prompt-local-backend.py
: <Response [200]>
**** Non-working configuration
(The easiest way to use the local backend is to use a TCP connection, not a socket, [[b1669606-710b-4fff-b97e-6bebdfa001d1][according to Christian Grothoff]]. That requires blocking the port to only allow local connections)
#+name: blk-configure-backend-local-socket.sh
#+caption: Configure local socket
#+begin_SRC bash
taler-config -s MERCHANT -o SERVE -V UNIX
taler-config -s MERCHANT -o UNIXPATH -V $SRVDIR
sudo mkdir $SRVDIR
sudo chown postgres:postgres $SRVDIR
#+end_SRC
Now, I don't know how to start it
#+caption: This seems to work (start the service)
#+begin_SRC bash :results output :dir "/sudo::" :cache no
sudo rc-service postgresql start
sudo -u postgres taler-merchant-httpd
#+end_SRC
#+begin_SRC bash :results raw
curl --no-progress-meter http://localhost:8888/ 2>&1
#+end_SRC
#+RESULTS:
: curl: (7) Failed to connect to localhost port 8888: Connection refused
To check my port:
#+begin_SRC bash :dir "/sudo::" :cache no
lsof -i:8888
lsof -i -P -n | grep 8888
#+end_SRC
*** Allow access only to localhost on port 8888 [2020-08-04 mar]
:LOGBOOK:
CLOCK: [2020-08-04 mar 20:30]--[2020-08-04 mar 20:38] => 0:06
:END:
- [[https://stefanauwyang.com/setting-iptables-to-allow-port-connection-from-localhost-only/][Stefan's website]]
#+begin_QUOTE
In this example we want to configure port 8080 to be accessible from localhost only. These are the steps:
1. Execute this command to accept connection from localhost.
#+begin_SRC bash
iptables -A INPUT -p tcp -s localhost --dport 8080 -j ACCEPT
#+end_SRC
1. Execute this command to drop any connection from other hosts.
#+begin_SRC bash
iptables -A INPUT -p tcp --dport 8080 -j DROP
#+end_SRC
If we want to undo this changes, we can execute the same command by replacing -A with -D. From here we may reverse proxy our 8080 port using our apache http server.
#+end_QUOTE
- [[https://www.linuxquestions.org/questions/ubuntu-63/what-ufw-rule-will-allow-port-80-to-localhost-but-only-from-localhost-4175595450/#post5642286][Allow port 80 to localhost but only from localhost]] (linuxquestions)
#+begin_SRC bash
ufw allow from 127.0.0.1 to 127.0.0.1 port 80 proto tcp
#+end_SRC
- https://stackoverflow.com/questions/28170004/how-to-do-local-port-forwarding-with-iptables
- https://wiki.gentoo.org/wiki/Security_Handbook/Firewalls
*** DONE Try to do something with Taler (web backend)
CLOSED: [2020-08-02 dom 17:09]
:LOGBOOK:
CLOCK: [2020-08-02 dom 16:40]--[2020-08-02 dom 17:09] => 0:29
CLOCK: [2020-08-02 dom 16:00]--[2020-08-02 dom 16:36] => 0:36
CLOCK: [2020-08-02 dom 11:02]--[2020-08-02 dom 11:10] => 0:08
CLOCK: [2020-08-02 dom 09:41]--[2020-08-02 dom 10:22] => 0:41
CLOCK: [2020-08-02 dom 09:10]--[2020-08-02 dom 09:18] => 0:08
:END:
- [[file:docs/taler-merchant-api-tutorial.rst][documentation]]
I can use Python:
#+name: blk-proxyurl.py
#+caption: Get the value of a proxy (address:port) and set the default variable
#+begin_SRC python :noweb yes :results replace output
with open("proxy.url", "r") as fd:
proxyurl = fd.readlines()[0].rstrip()
proxies = {'http': proxyurl}
#+end_SRC
**** Prompt for backend
#+name: blk-taler-prompt-backend-tut.py
#+begin_SRC python :results output replace
import requests
<<blk-proxyurl.py>>
reqres = requests.get("https://backend.demo.taler.net",
headers={"Authorization": "ApiKey sandbox"},
proxies=proxies)
print(reqres)
#+end_SRC
#+RESULTS:
: <Response [200]>
**** Place an order
#+name: blk-taler-place-order-tut.py
#+caption: Place an order for 10 KUDOS as the /default/ merchant (instance)
#+begin_SRC python :results output replace :noweb yes
import requests
<<blk-proxyurl.py>>
order = {
"order": {"amount": "KUDOS:10",
"summary": "Donation",
"fulfillment_url":
"https://example.com/thanks.html"}}
order_resp = requests.post(
"https://backend.demo.taler.net/order", json=order,
headers={"Authorization": "ApiKey sandbox"},
proxies=proxies)
print(order_resp.json())
#+end_SRC
#+name: txt-taler-place-order-resp
#+RESULTS:
: {'order_id': '2020.215-00ADHA4815P58'}
**** Check payment
#+BEGIN_SRC python :noweb yes :results output replace :var ordjson=txt-taler-place-order-resp
import requests
# Get previous (text) result and create a dictionary with it
order_json = eval(ordjson)
<<blk-proxyurl.py>>
r = requests.get(
"https://backend.demo.taler.net/check-payment",
params=dict(order_id=order_json["order_id"]),
headers={"Authorization": "ApiKey sandbox"},
proxies=proxies)
print(r.json())
{'taler_pay_uri': 'taler://pay/backend.demo.taler.net/-/-/2020.215-00ADHA4815P58', 'contract_url': 'https://backend.demo.taler.net/public/proposal?instance=default&order_id=2020.215-00ADHA4815P58', 'paid': False, 'already_paid_order_id': None}
See
<> import requests postdata= { "payto_uris": ["payto://x-taler-bank/localhost/pos"], "id": "default", "name":"default", "address":{}, "jurisdiction":{}, "default_max_wire_fee": "KUDOS:1", "default_max_deposit_fee": "KUDOS:1", "default_wire_fee_amortization":1, "default_wire_transfer_delay":{"d_ms": 3600000}, "default_pay_delay": {"d_ms": 3600000}} reqres = requests.post("http://localhost:8888/private/instances", json=postdata) print(reqres.json())
<>
{'instances': [{'name': 'default', 'id': 'default', 'merchant_pub': '6266BFH77XGC1BGZVCS06J97REEY43SNR5AND65R2GGBAX82DCFG', 'payment_targets': ['x-taler-bank']}]}
curl http://backend.test.taler.net/private/instances
CLOSED: [2020-08-09 dom 22:22] :LOGBOOK: CLOCK: [2020-08-04 mar 12:41]--[2020-08-04 mar 13:30] => 0:49 CLOCK: [2020-08-03 lun 23:15]--[2020-08-03 lun 23:57] => 0:42 CLOCK: [2020-08-02 dom 19:01]--[2020-08-02 dom 20:21] => 1:20 CLOCK: [2020-08-02 dom 17:09]--[2020-08-02 dom 18:00] => 0:51 :END:
CLOSED: [2020-08-04 mar 22:32]
git init pretix-doc cd pretix-doc torsocks git remote add origin https://github.com/pretix/pretix git config core.sparsecheckout true echo "doc/*" >> .git/info/sparse-checkout torsocks git pull --depth=1 origin master
CLOSED: [2020-08-04 mar 22:32] :LOGBOOK: CLOCK: [2020-08-04 mar 20:56]--[2020-08-04 mar 22:25] => 1:29 :END:
torsocks pacman -S postfix postfix-openrc bind-openrc cyrus-sasl-openrc pacman -D --asdep postfix bind-openrc cyrus-sasl-openrc
#+begin_QUOTE Local mail
To only deliver mail to local system users (that are in /etc/passwd) update /etc/postfix/main.cf to reflect the following configuration. Uncomment, change, or add the following lines:
#+begin_SRC conf myhostname = localhost mydomain = localdomain mydestination = $myhostname, localhost.$mydomain, localhost inet_interfaces = $myhostname, localhost mynetworks_style = host default_transport = error: outside mail is not deliverable #+end_SRC
#+end_QUOTE
to set the hostname correctly,
#+begin_QUOTE
/etc/conf.d/hostname and /etc/conf.d/net are the files responsible for this. In this example, the mail server is named foo
on the domain example.com
#+end_QUOTE
#+caption: Setup domain name #+begin_SRC conf :file /etc/conf.d/net dns_domain_lo="example.com" #+end_SRC #+caption: Setup domain name #+begin_SRC conf :file /etc/conf.d/hostname HOSTNAME="foo" #+end_SRC
#+begin_QUOTE Before starting Postfix for the first time, the local alias database has to be compiled. If this is not done, Postfix may appear to have started normally, but won't work and the log (usually found in /var/log/mail.log) will be spammed with errors:
#+begin_EXAMPLE Mar 16 11:40:32 foo postfix/smtpd[18923]: fatal: open database /etc/mail/aliases.db: No such file or directory #+end_EXAMPLE
The alias database contains default local accounts required by various RFCs and common internet practice, as well as some pseudo accounts. Simply run the newaliases command to generate the database: #+begin_SRC bash newaliases #+end_SRC #+end_QUOTE
#+begin_QUOTE Most ... programs will not accept an email using just @localhost as domain ... edit /etc/hosts file to make the domain localhost.com point to your machine ...:
#+begin_SRC conf
127.0.0.1 localhost.com
#+end_SRC
#+end_QUOTE
The /etc/hosts
file seems independent of the init system (OpenRC, SystemD...):
#+begin_QUOTE
/etc/hosts ... is a simple text file that associates IP addresses with hostnames, one line per IP address
-- HOSTS(5) -- 2020-06-09 -- Linux -- Linux Programmer's Manual
#+end_QUOTE
#+begin_QUOTE Check configuration
Run the postfix check
command. It should output anything that you might have done wrong in a config file.
To see all of your configs, type postconf
. To see how you differ from the defaults, try postconf -n.
#+begin_QUOTE Configure a Catch-all Address
Enabling this, you can use any email address ending with "@localhost" or "@localhost.com".
Example: here, my unique account is rael@localhost.com. But while testing systems, I can use any address like joe@localhost.com, foo@localhost.com, etc, because all will be redirected to rael@localhost.com
If (it does) not exists, create (the) file /etc/postfix/virtual=:
... Add the following 2 lines ...
#+end_QUOTE
#+name: blk-postfix-virtual.conf
#+begin_SRC conf
# Replace <your-user>
@localhost <your-user>
@localhost.com <your-user>
#+end_SRC
#+begin_QUOTE
Configure postifx to read this file: ... /etc/postfix/main.cf: ...
And check if this line is enabled, or add it if not exists:
#+end_QUOTE
#+begin_SRC conf
virtual_alias_maps
hash:/etc/postfix/virtual
#+end_SRC
#+begin_QUOTE
Activate it:
#+end_QUOTE
#+begin_SRC bash
sudo postmap /etc/postfix/virtual
#+end_SRC
#+begin_QUOTE
Reload postfix:
#+end_QUOTE
#+caption: not from referenced website (replaced for OpenRC)
#+begin_SRC bash
sudo rc-service postfix restart
#+end_SRC
#+begin_SRC emacs-lisp ;;send mail using postfix (setq send-mail-function 'sendmail-send-it) (setq message-send-mail-function 'message-send-mail-with-sendmail) #+end_SRC
#+begin_SRC bash # As super-user netstat -tulpn | grep 25 tail -f /var/log/maillog #+end_SRC
#+begin_QUOTE Reading the messages in an Rmail file is done in a special major mode, Rmail mode, which redefines most letters to run commands for managing mail. #+end_QUOTE
#+begin_QUOTE Using Rmail in the simplest fashion, you have one Rmail file ‘~/RMAIL’ in which all of your mail is saved. It is called your “primary Rmail file”. The command ‘M-x rmail’ reads your primary Rmail file, merges new mail in from your inboxes, displays the first message you haven’t read yet, and lets you begin reading. The variable ‘rmail-file-name’ specifies the name of the primary Rmail file. #+end_QUOTE
#+begin_QUOTE When you receive mail locally, the operating system places incoming mail for you in a file that we call your “inbox”. When you start up Rmail, it runs a C program called ‘movemail’ to copy the new messages from your inbox into your primary Rmail file, which also contains other messages saved from previous Rmail sessions. It is in this file that you actually read the mail with Rmail. This operation is called “getting new mail”. You can get new mail at any time in Rmail by typing ‘g’.
The variable ‘rmail-primary-inbox-list’ contains a list of the files that are inboxes for your primary Rmail file. If you don’t set this variable explicitly, Rmail uses the ‘MAIL’ environment variable, or, as a last resort, a default inbox based on ‘rmail-spool-directory’. The default inbox file depends on your operating system; often it is ‘/var/mail/USERNAME’, ‘/var/spool/mail/USERNAME’, or ‘/usr/spool/mail/USERNAME’. #+end_QUOTE
#+begin_SRC emacs-lisp (message rmail-spool-directory) #+end_SRC
#+caption: Directory for inbox in this computer #+RESULTS:
/var/mail/
#+begin_QUOTE Mailutils version is able to handle a wide set of mailbox formats, such as plain Unix mailboxes, maildir and MH mailboxes, etc. It is able to access remote mailboxes using the POP3 or IMAP4 protocol, and can retrieve mail from them using a TLS encrypted channel. #+end_QUOTE
#+begin_SRC bash sudo torsocks pacman -S mailutils #+end_SRC
#+begin_QUOTE Rmail operates by default on your primary Rmail file, which is named ~/RMAIL and receives your incoming mail from your system inbox file. #+end_QUOTE
#+begin_QUOTE If ‘rmail-preserve-inbox’ is non-‘nil’, then Rmail does not clear out the inbox file when it gets new mail. #+end_QUOTE
#+caption: Keep incoming e-mail in the (local) mail server (postfix) #+begin_SRC emacs-lisp (setq rmail-preserve-inbox t) #+end_SRC
#+caption: Set mail server (from EmacsWiki) #+begin_SRC emacs-lisp (setenv "MAILHOST" "localhost") #+end_SRC
#+caption: Send an empty e-mail to self on this computer #+begin_SRC bash mail -s "Hello world 1" $USER@localhost #+end_SRC
Launch rmail
in Emacs (M-x rmail
)
#+begin_SRC mail From @localhost Tue Aug 4 22:13:06 2020 Return-Path: <@localhost> X-Original-To: @localhost Delivered-To: @localhost Received: by localhost (Postfix, from userid 1000) id AB13C5C779D; Tue, 4 Aug 2020 22:13:06 -0500 (CDT) Date: Tue, 04 Aug 2020 22:13:06 -0500 To: @localhost Subject: Hello world 1 User-Agent: mail v14.9.19 Message-Id: <20200805031306.AB13C5C779D@localhost> From: @localhost X-RMAIL-ATTRIBUTES: -------- #+end_SRC
CLOSED: [2020-08-08 sáb 23:03] :LOGBOOK: CLOCK: [2020-08-08 sáb 12:14]--[2020-08-08 sáb 19:38] => 7:24 CLOCK: [2020-08-06 jue 14:41]--[2020-08-06 jue 15:06] => 0:25 CLOCK: [2020-08-05 mié 21:20]--[2020-08-05 mié 21:54] => 0:34 CLOCK: [2020-08-05 mié 19:24]--[2020-08-05 mié 20:05] => 0:41 :END:
torsocks pacman -S nginx-openrc
#+begin_QUOTE A Nginx HTTPS reverse proxy is an intermediary proxy service which takes a client request, passes it on to one or more servers, and subsequently delivers the server’s response back to the client. While most common applications are able to run as web server on their own, the Nginx web server is able to provide a number of advanced features such as load balancing, TLS/SSL capabilities and acceleration #+end_QUOTE
#+begin_QUOTE nginx (pronounced "engine X"), is a free, open-source, high-performance HTTP server and reverse proxy, as well as an IMAP/POP3 proxy server, written by Igor Sysoev in 2005. nginx is well known for its stability, rich feature set, simple configuration, and low resource consumption. #+end_QUOTE
#+begin_QUOTE Get a Certificate
Next, you will need to purchase or create an SSL certificate. These commands are for a self-signed certificate, but you should get an officially signed certificate if you want to avoid browser warnings. #+end_QUOTE
#+begin_QUOTE *Warning*: If you plan on implementing TLS, know that some variations and implementations are still vulnerable to attack[1]. For details on these current vulnerabilities within TLS and how to apply appropriate changes to nginx, visit https://weakdh.org/sysadmin.html #+end_QUOTE
#+begin_QUOTE
Create a private key and self-signed certificate. This is adequate for most installations that do not require a CSR:
#+end_QUOTE
#+begin_SRC bash
# as root
mkdir /etc/nginx/ssl
cd /etc/nginx/ssl
openssl req -new -x509 -nodes -newkey rsa:4096 -keyout server.key -out server.crt -days 1095
chmod 400 server.key
chmod 444 server.crt
#+end_SRC
#+begin_QUOTE
Note: The -days
switch is optional and RSA keysize can be as low as 2048 (default).
#+end_QUOTE
#+begin_QUOTE Now that we have the certificates, we can change the configuration in nginx to serve via HTTPS. The HTTPS connection is done via the port 443. The first server block in nginx configuration at /etc/nginx/sites-available/default is to redirect the HTTP traffic to HTTPS. We are also returning a 301 Moved Permanently header back. Replace the domain-name here to your domain. #+end_QUOTE #+caption: not used (only to compare below) #+begin_SRC conf server { listen 80; server_name ; return 301 https://$host$request_uri; } #+end_SRC
#+caption: Define localhost
as an HTTPS server with redirection in /etc/nginx/nginx.conf
(inside http {
)
#+begin_SRC bash
# Redirect to HTTPS
server {
listen 80;
server_name localhost;
return 301 https://$host$request_uri;
}
#+end_SRC
#+begin_QUOTE The second server block is for HTTPS running at port 443. It uses the certificates found at /etc/letsencrypt/live/ #+end_QUOTE #+caption: not used (only to compare below) #+begin_SRC conf server { listen 443 ssl; server_name ;
ssl_certificate /etc/letsencrypt/live//fullchain.pem; ssl_certificate_key /etc/letsencrypt/live//privkey.pem;
root /usr/share/nginx/html; index index.html index.htm index.nginx-debian.html;
location / { try_files $uri $uri/ =404; }
location ^~ /.well-known/ { allow all; } } #+end_SRC
#+begin_SRC conf server { #listen 80; # Uncomment to also listen for HTTP requests listen 443 ssl http2; # HTTP/2 is only possible when using SSL server_name localhost;
ssl_certificate ssl/server.crt; ssl_certificate_key ssl/server.key;
root usr/share/nginx/html; location { index index.html index.htm; } #+end_SRC
CLOSED: [2020-08-07 vie 21:36] :LOGBOOK: CLOCK: [2020-08-06 jue 15:06]--[2020-08-06 jue 15:37] => 0:31 :END:
torsocks pacman -S redis torsocks pacman -D --asdep nginx-mod-redis{,2} python-redis
#+begin_QUOTE in-memory data structure store, used as a database, cache and message broker. #+end_QUOTE
#+begin_SRC bash torsocks curl -LO https://raw.githubusercontent.com/artix-linux/community/master/redis-openrc/trunk/PKGBUILD #+end_SRC
_inst_confd(){ #+end_SRC
#+name: blk-ufw-restrict-port.sh
#+caption: Restrict port 6379
(see /etc/redis.conf
) to localhost only
#+begin_SRC bash :var localport="6379" :dir "/sudo::" :cache no
ufw allow from 127.0.0.1 to 127.0.0.1 port $localport proto tcp
#+end_SRC
CLOSED: [2020-08-08 sáb 23:03] :LOGBOOK: CLOCK: [2020-09-01 mar 18:56]--[2020-09-01 mar 20:06] => 1:10 CLOCK: [2020-08-18 mar 18:17]--[2020-08-18 mar 19:10] => 0:53 CLOCK: [2020-08-08 sáb 22:57]--[2020-08-08 sáb 23:03] => 0:06 CLOCK: [2020-08-08 sáb 21:05]--[2020-08-08 sáb 22:42] => 1:41 CLOCK: [2020-08-08 sáb 19:38]--[2020-08-08 sáb 19:49] => 0:11 CLOCK: [2020-08-08 sáb 11:05]--[2020-08-08 sáb 11:30] => 0:25 CLOCK: [2020-08-07 vie 20:00]--[2020-08-07 vie 21:40] => 1:40 CLOCK: [2020-08-07 vie 18:47]--[2020-08-07 vie 19:15] => 0:28 CLOCK: [2020-08-07 vie 17:43]--[2020-08-07 vie 18:44] => 1:01 CLOCK: [2020-08-06 jue 21:16]--[2020-08-06 jue 21:26] => 0:10 CLOCK: [2020-08-06 jue 19:15]--[2020-08-06 jue 20:17] => 1:02 CLOCK: [2020-08-06 jue 18:37]--[2020-08-06 jue 19:13] => 0:36 CLOCK: [2020-08-06 jue 17:10]--[2020-08-06 jue 17:13] => 0:03 CLOCK: [2020-08-06 jue 16:28]--[2020-08-06 jue 17:06] => 0:38 CLOCK: [2020-08-06 jue 15:37]--[2020-08-06 jue 15:55] => 0:18 :END:
#+caption: I used useradd
instead of adduser
(not part of instructions)
#+begin_SRC bash :var pretixhome="/var/pretix" :dir "/sudo::" :cache no
useradd -M -N --shell /usr/bin/nologin -d "$pretixhome" pretix
mkdir "$pretixhome"
chown pretix "$pretixhome"
#+end_SRC
#+begin_SRC bash :dir "/sudo::" :cache no sudo -u postgres createuser pretix sudo -u postgres createdb -O pretix pretix #+end_SRC
#+begin_SRC bash mkdir /etc/pretix touch /etc/pretix/pretix.cfg chown -R pretix:pretix /etc/pretix/ chmod 0600 /etc/pretix/pretix.cfg #+end_SRC
#+begin_SRC conf :file /etc/pretix/pretix.cfg [pretix] instance_name=My pretix installation url=https://pretix.mydomain.com currency=KUDOS ; `data' needs to be located in the root directory of the ; `pretix' user of the system. In this case `/var/pretix' datadir=/var/pretix/data trust_x_forwarded_for=on trust_x_forwarded_proto=on
[database] ; For MySQL, replace with "mysql" backend=postgresql name=pretix user=pretix ; we can use peer authentification if our ; PostgreSQL user matches our unix user. password= ; For local postgres authentication, you can leave it empty host=
[mail] ; See config file documentation for more options from=tickets@localhost host=127.0.0.1
[redis] location=redis://127.0.0.1/0 sessions=true
[celery] backend=redis://127.0.0.1/1 broker=redis://127.0.0.1/2 #+end_SRC
After long deliveration with myself (as the worked progressed), the decision was to skip sanity on my operating system (use virtualenv
), because the project needs to be working first. Once we get it going, there may be time to audit all the packages which are needed. For instance, note how the Git version of Pretix needs different packages than the one which is distributed on PyPi.com, not to mention the dependencies of those packages themselves.
#+begin_SRC bash torsocks curl -LO https://aur.archlinux.org/cgit/aur.git/snapshot/python-django-bootstrap3.tar.gz #+end_SRC
#+caption: Turn (all!) the requirements from Pretix into a format suitable for a PKGBUILD #+RESULTS: #+begin_example # Functional requirements Django==3.0.*,>=3.0.9 'djangorestframework>=3.11' 'djangorestframework<3.12' 'python-dateutil>=2.8' 'python-dateutil<2.9' isoweek 'requests=2.24.0' pytz 'django-bootstrap3>=12.0' 'django-bootstrap3<12.1' 'django-formset-js-improved=0.5.0.2' 'django-compressor>=2.4' 'django-compressor<2.5' django-hierarkey==1.0.*,>=1.0.4 'django-filter>=2.2' 'django-filter<2.3' 'django-scopes>=1.2' 'django-scopes<1.3' 'reportlab>>=3.5.18' 'reportlab><3.5.19' 'PyPDF2>=1.26' 'PyPDF2<1.27' 'Pillow>=7' 'Pillow<8' 'django-libsass=0.8' 'libsass=0.19.2' django-otp==0.7.*,>=0.7.5 'python-u2flib-server>=4' 'python-u2flib-server<5' 'webauthn>=0.4' 'webauthn<0.5' 'django-formtools=2.2' 'celery>=4.4' 'celery<4.5' 'kombu>=4.6' 'kombu<4.7' 'django-statici18n>=1.9' 'django-statici18n<1.10' 'inlinestyler>=0.2' 'inlinestyler<0.3' 'BeautifulSoup4>=4.8' 'BeautifulSoup4<4.9' slimit lxml 'static3>=0.7' 'static3<0.8' dj-static csscompressor django-markup markdown<=2.2 bleach>=3.1.4,<3.2.0 'sentry-sdk>=0.14' 'sentry-sdk<0.15' babel django-i18nfield>=1.7.0 django-hijack>=2.1.10,<2.2.0 jsonschema 'openpyxl>=3.0' 'openpyxl<3.1' 'django-oauth-toolkit>=1.2' 'django-oauth-toolkit<1.3' 'oauthlib>=3.1' 'oauthlib<3.2' django-jsonfallback>=2.1.2 psycopg2-binary 'tqdm>=4' 'tqdm<5' # Stripe 'stripe>=2.42' 'stripe<2.43' # PayPal 'paypalrestsdk>=1.13' 'paypalrestsdk<1.14' 'pycparser=2.13' # Banktransfer chardet<3.1.0,>=3.0.2 'mt-940=3.2' 'vobject>=0.9' 'vobject<0.10' pycountry django-countries>=6.0 pyuca defusedcsv>=1.1.0 'vat_moss_forked=2020.3.20.0.11.0' django-localflavor>=2.2 'django-redis>=4.11' 'django-redis<4.12' 'redis>=3.4' 'redis<3.5' 'django-phonenumber-field>=4.0' 'django-phonenumber-field<4.1' 'phonenumberslite>=8.11' 'phonenumberslite<8.12' 'python-bidi>=0.4' 'python-bidi<0.5' 'arabic-reshaper=2.0.15' packaging tlds>=2020041600 'text-unidecode>=1' 'text-unidecode<2' #+end_example
#+caption: PKGBUILD for Pretix #+begin_SRC pkgbuild pkgname="python-pretix" _pkgname="pretix" pkgver=3.11.0 pkgrel=dev0 pkgdesc="Ticketing software that cares about your event--all the way." arch=('x86_64') url="https://github.com/pretix/pretix" license=('BSD') depends=( 'gunicorn' 'postgresql' # From production.txt in the tar-ball 'python-django>=3.0.9' 'python-django<3.1' 'python-django-rest-framework>=3.11' 'python-django-rest-framework<3.12' 'python-dateutil>=2.8' 'python-dateutil>=2.8' 'python-isoweek' 'python-requests=2.24.0' 'python-pytz' 'python-django-bootstrap3>=12.0' 'python-django-bootstrap3<12.1' 'python-django-formset-js-improved=0.5.0.2' 'python-django-compressor>=2.4' 'python-django-compressor<2.5' 'python-django-filter>=2.2' 'python-django-filter<2.3' 'python-scopes>=1.2' 'python-scopes<1.3' 'python-reportlab>=3.5.18' 'python-reportlab<3.6' 'python-pillow>=7' 'python-pillow<8' 'python-django-libsass=0.8' 'python-django-libsass' 'python-libsass' # https://github.com/sass/libsass/issues/3053 is fixed 'python-django-otp>=0.7.5' 'python-django-otp<0.8' 'python-u2flib-server>=4' 'python-u2flib-server<5' 'python-webauthn>=0.4' 'python-webauthn<0.5' 'python-django-formtools=2.2' 'python-celery>=4.4' 'celery<4.5' 'python-kombu>=4.6' 'python-kombu<4.7' 'python-django-statici18n>=1.9' 'python-django-statici18n<1.10' 'python-inlinestyler>=0.2' 'python-inlinestyler<0.3' 'python-beautifulsoup4>=4.8' 'python-beautifulsoup4<4.9' 'python-slimit' 'python-lxml' 'python-static3>=0.7' 'python-static3<0.8' 'python-dj-static' 'python-csscompressor' 'python-django-markup' 'python-markdown<=2.2' 'python-bleach>=3.1.4','python-bleach<3.2.0' 'python-sentry-sdk>=0.14' 'python-sentry-sdk<0.15' 'python-babel' 'python-django-i18nfield>=1.7.0' 'python-django-hijack>=2.1.10','python-django-hijack<2.2.0' 'python-jsonschema' 'python-openpyxl>=3.0' 'openpyxl<3.1' 'python-django-oauth-toolkit>=1.2' 'python-django-oauth-toolkit<1.3' 'python-oauthlib>=3.1' 'python-oauthlib<3.2' 'python-django-jsonfallback>=2.1.2' # psycopg2-binary # not recommended for production (PyPi) 'python-psycopg2' 'python-tqdm>=4' 'python-tqdm<5' # Stripe 'python-stripe>=2.42' 'python-stripe<2.43' # PayPal 'python-paypalrestsdk>=1.13' 'python-paypalrestsdk<1.14' 'python-pycparser=2.13' # Banktransfer 'python-chardet<3.1.0','python-chardet>=3.0.2' 'python-mt-940=3.2' 'python-vobject>=0.9' 'python-vobject<0.10' 'python-pycountry' 'python-django-countries>=6.0' 'python-pyuca' 'python-defusedcsv>=1.1.0' 'python-vat_moss_forked=2020.3.20.0.11.0' 'python-django-localflavor>=2.2' 'python-django-redis>=4.11' 'python-django-redis<4.12' 'python-redis>=3.4' 'python-redis<3.5' 'django-phonenumber-field>=4.0' 'django-phonenumber-field<4.1' 'python-phonenumberslite>=8.11' 'python-phonenumberslite<8.12' 'python-bidi>=0.4' 'python-bidi<0.5' 'arabic-reshaper=2.0.15' 'python-packaging' 'python-tlds>=2020041600' 'python-text-unidecode>=1' 'python-text-unidecode<2' ) optdepends=() makedepends=('python' 'python-setuptools') source=("$pkgname-$pkgver.tar.gz::https://files.pythonhosted.org/packages/48/de/fc0da1bffd06be70f67dd4f65778955ab0afb7d3f0ebad3d2d194c9998ab/pretix-3.10.0.tar.gz") sha256sums=('e25c579e1c347b5eb5e4cf1dd19f38f4d1753f036ba4bb316ef6ff26dc4bad45')
build() { cd "$srcdir/${_pkgname}-${pkgver}" python setup.py build }
package() { cd "$srcdir/${_pkgname}-${pkgver}" python setup.py install --root="$pkgdir"/ --optimize=1 --skip-build install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE.txt" } #+end_SRC
#+begin_SRC bash :var pretixhome="/var/pretix" sudo -u pretix python3 -m venv "$pretixhome"/venv source "$pretixhome"/venv/bin/activate sudo -u pretix pip3 install -U pip setuptools wheel sudo -u pretix pip3 install "pretix[postgres]" gunicorn # WARNING: pretix 3.10.0 does not provide the extra 'postgres' # Successfully installed BeautifulSoup4-4.8.2 Django-3.0.9 Pillow-7.2.0 PyPDF2-1.26.0 amqp-2.6.1 arabic-reshaper-2.0.15 asgiref-3.2.10 attrs-19.3.0 babel-2.8.0 billiard-3.6.3.0 bleach-3.1.5 cbor2-5.1.2 celery-4.4.7 certifi-2020.6.20 cffi-1.14.1 chardet-3.0.4 cryptography-3.0 csscompressor-0.9.5 cssutils-1.0.2 defusedcsv-1.1.0 dj-static-0.0.6 django-appconf-1.0.4 django-bootstrap3-12.0.3 django-compat-1.0.15 django-compressor-2.4 django-countries-6.1.2 django-filter-2.2.0 django-formset-js-improved-0.5.0.2 django-formtools-2.2 django-hierarkey-1.0.4 django-hijack-2.1.10 django-i18nfield-1.8.0 django-jquery-js-3.1.1 django-jsonfallback-2.1.2 django-libsass-0.8 django-localflavor-3.0.1 django-markup-1.5 django-mysql-3.8.1 django-oauth-toolkit-1.2.0 django-otp-0.7.5 django-phonenumber-field-4.0.0 django-redis-4.11.0 django-scopes-1.2.0 django-statici18n-1.9.0 djangorestframework-3.11.1 et-xmlfile-1.0.1 future-0.18.2 gunicorn-20.0.4 idna-2.8 inlinestyler-0.2.5 isoweek-1.3.3 jdcal-1.4.1 jsonschema-3.2.0 kombu-4.6.11 libsass-0.19.2 lxml-4.5.2 markdown-2.2.0 mt-940-3.2 oauthlib-3.1.0 openpyxl-3.0.4 packaging-20.4 paypalrestsdk-1.13.1 phonenumberslite-8.11.5 ply-3.11 pretix-3.10.0 psycopg2-binary-2.8.5 pycountry-20.7.3 pycparser-2.13 pyopenssl-19.1.0 pyparsing-2.4.7 pyrsistent-0.16.0 python-bidi-0.4.2 python-dateutil-2.8.1 python-stdnum-1.13 python-u2flib-server-4.0.1 pytz-2020.1 pyuca-1.2 pyyaml-5.3.1 rcssmin-1.0.6 redis-3.4.1 reportlab-3.5.47 requests-2.22.0 rjsmin-1.1.0 sentry-sdk-0.14.4 six-1.15.0 slimit-0.8.1 soupsieve-2.0.1 sqlparse-0.3.1 static3-0.7.0 stripe-2.42.0 text-unidecode-1.3 tlds-2020080602 urllib3-1.25.10 vat-moss-forked-2020.3.20.0.11.0 vine-1.3.0 vobject-0.9.6.1 webauthn-0.4.7 webencodings-0.5.1 sudo -u pretix mkdir -p "$pretixhome"/data/media sudo rc-service postgresql start sudo rc-service redis start sudo -u pretix python -m pretix migrate python -m pretix rebuild # [...] # Invalid template /var/pretix/venv/lib/python3.8/site-packages/django_filters/templates/django_filters/rest_framework/crispy_form.html: 'crispy_forms_tags' is not a registered tag library. Must be one of: # [...] # Invalid template /var/pretix/venv/lib/python3.8/site-packages/django_otp/templates/otp/admin19/login.html: 'admin_static' is not a registered tag library. Must be one of: # [...] # Error parsing template /var/pretix/venv/lib/python3.8/site-packages/django_otp/plugins/otp_totp/templates/otp_totp/admin/config.html: admin/base_site.html # Error parsing template /var/pretix/venv/lib/python3.8/site-packages/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html: Invalid template name in 'extends' tag: ''. Got this from the 'basetpl' variable. # Error parsing template /var/pretix/venv/lib/python3.8/site-packages/django_otp/templates/otp/admin30/login.html: admin/base_site.html # Error parsing template /var/pretix/venv/lib/python3.8/site-packages/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_assign.html: Invalid template name in 'extends' tag: ''. Got this from the 'basetpl' variable. # Error parsing template /var/pretix/venv/lib/python3.8/site-packages/pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/job_detail.html: Invalid template name in 'extends' tag: ''. Got this from the 'basetpl' variable. # [...] # Compressed 22 block(s) from 407 template(s) for 0 context(s). #+end_SRC
#+begin_SRC bash /etc/init.d/celery-openrc #!/sbin/openrc-run supervisor=supervise-daemon
description="celery queue worker"
${CELERY_USER:="celery"} ${CELERY_GROUP:="$(id -gn $LS_USER)"}
pidfile="/run/$RC_SVCNAME.sd.pid" supervise_daemon_args="-u $CELERY_USER -g $CELERY_GROUP -p $pidfile" command=/usr/bin/celery command_args="${CELERY_OPTS}"
depends() { use net } #+end_SRC
#+begin_SRC conf /etc/conf.d/celery.confd CELERY_USER=celery CELERY_LOG=/var/log/celery.log CELERY_REDIR="1>/dev/null 2>> ${CELERY_LOG}" CELERY_OPTS="-A celeryapp -B --loglevel=info $CELERY_REDIR" #+end_SRC
# /etc/init.d/pretix-web # Distributed under the terms of the GNU General Public License v3
# opts="start stop restart"
name="Pretix web service through gunicorn" command="/var/pretix/venv/bin/gunicorn" command_args="pretix.wsgi --name pretix --workers 5 --max-requests 1200 --max-requests-jitter 50 --log-level=info --bind=127.0.0.1:8345" pidfile="/run/${RC_SVCNAME}.pid" # Tell OpenRC to send pretix to go into background and # create a pid file (pretix does not do it automatically) command_background=true # Use this user and group (create before hand) command_user="pretix" # Run in this directory directory="/var/pretix"
export VIRTUAL_ENV="/var/pretix/venv" export PATH="/var/pretix/venv/bin:$PATH"
depends() { use net } #+end_SRC
#+begin_QUOTE WSGI is the Web Server Gateway Interface. It is a specification that describes how a web server communicates with web applications, and how web applications can be chained together to process one request.
WSGI is a Python standard described in detail in PEP 3333. -- WSGI Read the docs #+end_QUOTE
#+name: blk-pretix-worker.openrc #+begin_SRC sh :file /etc/init.d/pretix-worker :tangle "media/pretix-worker" :mode 755 #!/sbin/openrc-run
# /etc/init.d/pretix-worker # Distributed under the terms of the GNU General Public License v3 supervisor=supervise-daemon # (Based on `celery-openrc' from Alpine Linux--Apache 2.0 license)
description="Pretix queue worker through celery"
${PRETIX_USER:="pretix"} ${CELERY_LOG:="/var/log/celery.log"} ${CELERY_OPTS:="-A pretix.celery_app worker -l info"}
pidfile="/run/${RC_SVCNAME}.sd.pid" supervise_daemon_args="-u $PRETIX_USER -p ${pidfile}" command="/var/pretix/venv/bin/celery" command_args="${CELERY_OPTS}" # Use this user and group (create before hand) command_user="pretix" # Run in this directory directory="/var/pretix"
export VIRTUAL_ENV="/var/pretix/venv" export PATH="/var/pretix/venv/bin:$PATH"
depends() { use net } #+end_SRC
chown pretix:root /var/log/celery.log
#+CAPTION: Try to set Bash environment variables to choose a preferred editor (sudo -E
takes the running user's environment)
#+begin_SRC bash
VISUAL=emacsclient EDITOR=emacsclient sudo -E -u pretix crontab -e
VISUAL=nano EDITOR=nano sudo -E -u pretix crontab -e
#+end_SRC
(remember that in vi, [ESC]
brings you into command mode, i
into insert mode, and :wq
saves. /var/pretix
is the root directory for the pretix
user)
#+begin_SRC fundamental
15,45 * * * * ps -u pretix -o args= | grep -q '\(celery.*-A pretix.celery_app worker\)' && export PATH=/var/pretix/venv/bin:$PATH && cd /var/pretix && python -m pretix runperiodic
#+end_SRC
#+begin_SRC bash :dir "/sudo::" :cache no rc-service pretix-web start rc-service pretix-worker start #+end_SRC
#+caption: Restrict port 8345
to localhost only
#+begin_SRC bash :var localport="8345" :noweb yes
<>
#+end_SRC
#+caption: Addendum to /etc/nginx/nginx.conf
(insde the http {...}
section). Remember that /var/pretix
is the home directory of the pretix
user.
#+begin_SRC conf
# Define a Pretix ipv6 server on my localhost
server {
listen 80 default_server;
listen [::]:80 ipv6only=on default_server;
server_name pretix.localhost.com;
}
server { listen 443 ssl localhost; listen [::]:443 ipv6only=on localhost; server_name pretix.localhost.com;
ssl_certificate ssl/server.crt; # relative to nginx dir ssl_certificate_key ssl/server.key;
add_header Referrer-Policy same-origin; add_header X-Content-Type-Options nosniff;
location / { proxy_pass http://localhost:8345/; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; proxy_set_header Host $http_host; }
location media { alias /var/pretix/data/media/; expires 7d; access_log off; }
location ^~ /media/cachedfiles { deny all; return 404; } location ^~ /media/invoices { deny all; return 404; }
location static { alias /var/pretix/venv/lib/python3.8/site-packages/pretix/static.dist/; access_log off; expires 365d; add_header Cache-Control "public"; } } #+end_SRC
Also check the relevant configuration for /etc/hosts
, /etc/conf.d/net
and /etc/conf.d/hostname
# Maintainer: Rafli Akmal # Contributor: artoo # Contributor: Oscar Campos
_url="https://raw.githubusercontent.com/gentoo/gentoo/master"
pkgname=redis-openrc pkgver=20180312 pkgrel=1 pkgdesc="OpenRC redis init script" arch=('any') url="https://github.com/artix-linux/packages-galaxy" license=('GPL2') depends=('openrc' 'redis') conflicts=('systemd-sysvcompat') backup=('etc/conf.d/redis') source=("redis.confd::${_url}/dev-db/redis/files/redis.confd-r1" "redis.initd::${_url}/dev-db/redis/files/redis.initd-5") sha256sums=('d995e4b96a4e11e8012edd470288f9b48b87b5a144a357bbaedd411c9bb1329d' 'b38e33e455719aa89bf19cda5cbcf093987a8dca82221e257cf642bcbb388ec0')
# pkgver() { # date +%Y%m%d # }
_inst_initd(){ install -Dm755 ${srcdir}/$1.initd ${pkgdir}/etc/init.d/$1
sed -e 's|#!/sbin/openrc-run|#!/usr/bin/openrc-run|g' \ -e 's|/var/run|/run|g' \ -e 's|/usr/sbin|/usr/bin|g' \ -i ${pkgdir}/etc/init.d/$1
install -d -m755 -o redis -g redis ${pkgdir}}/var/lib/redis }
_inst_confd(){ install -Dm755 ${srcdir}/$1.confd ${pkgdir}/etc/conf.d/$1 }
package() { _inst_confd 'redis' _inst_initd 'redis'
sed -e 's|/var/run|/run|g' \ -i "${pkgdir}/etc/conf.d/redis" }
# /etc/nginx/nginx.conf
worker_processes 1;
events { worker_connections 50; }
http { include mime.types; default_type application/octet-stream;
sendfile on; keepalive_timeout 65;
# Define a Redirect to HTTPS server { listen 80; server_name localhost; return 301 https://$host$request_uri;
error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }
# HTTPS server server { #listen 80; # Uncomment to also listen for HTTP requests listen 443 ssl http2; # HTTP/2 is only possible when using SSL server_name localhost;
ssl_certificate ssl/server.crt; ssl_certificate_key ssl/server.key;
root usr/share/nginx/html; location { index index.html index.htm; } }
# Define a Pretix ipv6 server on my localhost server { listen 80 default_server; listen [::]:80 ipv6only=on default_server; server_name pretix.localhost.com; } server { listen 443 ssl default_server; listen [::]:443 ipv6only=on default_server; server_name pretix.localhost.com;
ssl_certificate ssl/server.crt; ssl_certificate_key ssl/server.key;
add_header Referrer-Policy same-origin; add_header X-Content-Type-Options nosniff;
location / { proxy_pass http://localhost:8345/; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; proxy_set_header Host $http_host; }
location media { alias /var/pretix/data/media/; expires 7d; access_log off; }
location ^~ /media/cachedfiles { deny all; return 404; } location ^~ /media/invoices { deny all; return 404; }
location static { alias /var/pretix/venv/lib/python3.8/site-packages/pretix/static.dist/; access_log off; expires 365d; add_header Cache-Control "public"; } } }
<>
<>
#!/usr/bin/openrc-run
# /etc/init.d/pretix-load # Distributed under the terms of the GNU General Public License v3
# opts="start stop restart"
extra_commands="force_stop"
name="Services for Pretix" pidfile="/run/${RC_SVCNAME}.pid"
deps=( "postgresql" "nginx" "redis" "postfix" "pretix-worker" "pretix-web" ) # https://stackoverflow.com/a/13360181 indices=( ${!deps[@]} ) ndeps=${#indices[@]}
depends() { provide ${deps[@]}; }
roll_back() { for (( i = "$1" -1; i>=0; i-- )); do rc-service "${deps[i]}" stop; done; }
start () { for (( i = 0; i "$?" -ne "0" ; then einfo "Shutting down started services" roll_back "$i" && exit 1; fi; done; }
stop () { roll_back "$ndeps"; }
force_stop () { roll_back "$ndeps"; }
# /etc/postfix/main.cf
compatibility_level = 2
queue_directory = /var/spool/postfix
command_directory = /usr/bin
daemon_directory = /usr/lib/postfix/bin
data_directory = /var/lib/postfix
mail_owner = postfix
unknown_local_recipient_reject_code = 550
alias_maps = hash:/etc/postfix/aliases
alias_database = $alias_maps
debug_peer_level = 2
debugger_command
PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
ddd $daemon_directory/$process_name $process_id & sleep 5
sendmail_path
/usr/bin/sendmail
newaliases_path = /usr/bin/newaliases
mailq_path = /usr/bin/mailq
setgid_group = postdrop
html_directory = no
manpage_directory = /usr/share/man
sample_directory = /etc/postfix
readme_directory = /usr/share/doc/postfix
inet_protocols = ipv4
meta_directory = /etc/postfix
shlib_directory = /usr/lib/postfix
myhostname = localhost
mydomain = localdomain
mydestination = $myhostname, localhost.$mydomain, localhost
inet_interfaces = $myhostname, localhost
mynetworks_style = host
default_transport = error: outside mail is not deliverable
virtual_alias_maps = hash:/etc/postfix/virtual
myorigin = localhost
relayhost =
# /etc/postfix/virtual <>
CLOSED: [2020-09-01 mar 13:29] :LOGBOOK: CLOCK: [2020-08-20 jue 21:13]--[2020-08-20 jue 21:23] => 0:10 CLOCK: [2020-08-20 jue 20:25]--[2020-08-20 jue 21:13] => 0:48 :END:
I need to learn Django to use Pretix
:LOGBOOK: CLOCK: [2020-08-24 lun 22:13]--[2020-08-24 lun 22:45] => 0:32 CLOCK: [2020-08-24 lun 17:24]--[2020-08-24 lun 18:11] => 0:47 CLOCK: [2020-08-22 sáb 14:25]--[2020-08-22 sáb 14:36] => 0:11 CLOCK: [2020-08-21 vie 16:40]--[2020-08-21 vie 17:43] => 1:03 :END:
mkdir media/django
django-admin startproject mytut
python manage.py runserver 8080
The development server automatically reloads Python code for each request as needed. You don’t need to restart the server for code changes to take effect. However, some actions like adding files don’t trigger a restart, so you’ll have to restart the server in these cases. --
Your apps can live anywhere on your Python path
python manage.py startapp polls
:LOGBOOK: CLOCK: [2020-08-29 sáb 21:00]--[2020-08-29 sáb 22:09] => 1:09 CLOCK: [2020-08-28 vie 20:38]--[2020-08-28 vie 21:45] => 1:07 CLOCK: [2020-08-28 vie 13:41]--[2020-08-28 vie 14:28] => 0:47 CLOCK: [2020-08-28 vie 13:05]--[2020-08-28 vie 13:28] => 0:23 CLOCK: [2020-08-27 jue 18:25]--[2020-08-27 jue 19:37] => 1:12 CLOCK: [2020-08-27 jue 18:11]--[2020-08-27 jue 18:22] => 0:11 CLOCK: [2020-08-27 jue 17:25]--[2020-08-27 jue 17:48] => 0:23 CLOCK: [2020-08-27 jue 15:30]--[2020-08-27 jue 16:14] => 0:44 CLOCK: [2020-08-27 jue 14:45]--[2020-08-27 jue 15:25] => 0:40 CLOCK: [2020-08-26 mié 23:05]--[2020-08-27 jue 00:31] => 1:26 CLOCK: [2020-08-26 mié 12:08]--[2020-08-26 mié 12:59] => 0:51 CLOCK: [2020-08-25 mar 22:41]--[2020-08-25 mar 22:46] => 0:05 CLOCK: [2020-08-25 mar 22:15]--[2020-08-25 mar 22:40] => 0:25 CLOCK: [2020-08-25 mar 15:06]--[2020-08-25 mar 15:13] => 0:07 CLOCK: [2020-08-25 mar 13:53]--[2020-08-25 mar 14:44] => 0:51 CLOCK: [2020-08-21 vie 19:56]--[2020-08-21 vie 20:03] => 0:07 CLOCK: [2020-08-21 vie 19:31]--[2020-08-21 vie 19:56] => 0:25 CLOCK: [2020-08-20 jue 21:54]--[2020-08-20 jue 22:08] => 0:14 :END:
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the polls index.")
To call the view, we need to map it to a URL - and for this we need a URLconf.
touch polls/urls.py
from django.urls import path
from . import views
urlpatterns = [ path('', views.index, name='index'), ]
from django.contrib import admin from django.urls import include, path
urlpatterns = [ path('polls/', include('polls.urls')), path('admin/', admin.site.urls), ]
Now, I am modifying this to do something else
from django.urls import path
from . import views
urlpatterns = [ path('', views.index, {"arg1":"hello"}, name='index'), ]
from django.http import HttpResponse
def index(request, **kwargs): print("This will be printed on the (std)output") print(kwargs) msg = "Hello, world. You're at the polls index. You passed these args: " + str(kwargs) return HttpResponse(msg)
curl -s http://localhost:8000/polls/
Hello, world. You're at the polls index. You passed these args: {'arg1': 'hello'}
You would get this on the log (stdout or file) for the server (see blk blk-runserver-8080.py)
This will be printed on the (std)output {'arg1': 'hello'}
python manage.py migrate
Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying sessions.0001_initial... OK
sqlite3 -line db.sqlite3 '.schema' | head -n 5 && printf "[...]\n"
CREATE TABLE IF NOT EXISTS "django_migrations" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "app" varchar(255) NOT NULL, "name" varchar(255) NOT NULL, "applied" datetime NOT NULL); CREATE TABLE sqlite_sequence(name,seq); CREATE TABLE IF NOT EXISTS "auth_group_permissions" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "group_id" integer NOT NULL REFERENCES "auth_group" ("id") DEFERRABLE INITIALLY DEFERRED, "permission_id" integer NOT NULL REFERENCES "auth_permission" ("id") DEFERRABLE INITIALLY DEFERRED); CREATE TABLE IF NOT EXISTS "auth_user_groups" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "user_id" integer NOT NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED, "group_id" integer NOT NULL REFERENCES "auth_group" ("id") DEFERRABLE INITIALLY DEFERRED); CREATE TABLE IF NOT EXISTS "auth_user_user_permissions" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "user_id" integer NOT NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED, "permission_id" integer NOT NULL REFERENCES "auth_permission" ("id") DEFERRABLE INITIALLY DEFERRED); [...]
Models are represented by Python classes.
Each model has a number of class variables, each of which represents a database field in the model. ... Each field is represented by an instance of a Field class--e.g., CharField for character fields ... The name of each Field instance (e.g. question_text or pub_date) is ... in your Python code, and your database will use it as the column name.
from django.db import models
# Create your models here. class Question(models.Model): question_text = models.CharField(max_length=200) <>
class Choice(models.Model): <> <> <>
pub_date = models.DateTimeField('date published')
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
question = models.ForeignKey(Question, on_delete=models.CASCADE)
With this, Django is able to:
To include the app in our project, we need to add a reference to its configuration class in the setting
sed -i 's%"polls.apps.PollsConfig",%%g; s%\(INSTALLED_APPS(\)\(.*\)%\1"polls.apps.PollsConfig",\2%g' mytut/settings.py grep -A 10 PollsConfig mytut/settings.py
INSTALLED_APPS = ["polls.apps.PollsConfig", 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ]
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware',
python manage.py makemigrations polls
Migrations for 'polls': polls/migrations/0001_initial.py - Create model Question - Create model Choice
python manage.py sqlmigrate polls 0001
BEGIN; -- -- Create model Question -- CREATE TABLE "polls_question" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "question_text" varchar(200) NOT NULL, "pub_date" datetime NOT NULL); -- -- Create model Choice -- CREATE TABLE "polls_choice" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "choice_text" varchar(200) NOT NULL, "votes" integer NOT NULL, "question_id" integer NOT NULL REFERENCES "polls_question" ("id") DEFERRABLE INITIALLY DEFERRED); CREATE INDEX "polls_choice_question_id_c5b4b260" ON "polls_choice" ("question_id"); COMMIT;
python manage.py check
python manage.py migrate
Operations to perform: Apply all migrations: admin, auth, contenttypes, polls, sessions Running migrations: Applying polls.0001_initial... OK
Migrations are very powerful and let you change your models over time, as you develop your project, without the need to delete your database or tables and make new ones - it specializes in upgrading your database live, without losing data ... remember the three-step guide to making model changes:
python manage.py shell
from polls.models import Choice, Question
Question.objects.all()
from django.utils import timezone q = Question(question_text="What's new?", pub_date=timezone.now())
from django.db import models
# Create your models here. class Question(models.Model): question_text = models.CharField(max_length=200) <> def __str__(self): return self.question_text def was_published_recently(self): return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
class Choice(models.Model): <> <> <> def __str__(self): return self.choice_text
def was_published_recently(self): return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
Question.objects.all() Question.objects.filter(id=1) Question.objects.filter(question_text__startswith='What') help(Question.objects.filter)
from polls.models import Choice, Question q = Question.objects.get(pk=1) q.choice_set.all() q.choice_set.create(choice_text='Not much', votes=0) q.choice_set.create(choice_text='The sky', votes=0) c = q.choice_set.create(choice_text='Just hacking again', votes=0) c.question q.choice_set.all()
c = q.choice_set.filter(choice_text='Not much')[1:] [VAL.delete() for VAL in c]
q.choice_set.get(choice_text__startswith="Not much")
Django entirely automates creation of admin interfaces for models
python manage.py createsuperuser
from django.contrib import admin
# Register your models here.
from .models import Question
admin.site.register(Question)
To get from a URL to a view, Django uses what are known as ‘URLconfs’. A URLconf maps URL patterns to views.
def detail(request, question_id): return HttpResponse("You're looking at question %s." % question_id)
def results(request, question_id):
response = "You're looking at the results of question %s."
return HttpResponse(response % question_id)
def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)
#+end_SRC
#+CAPTION: Create 3 views (webpages) to show and vote on the poll
#+begin_SRC python :tangle media/django/mytut/polls/views.py :noweb yes
<<blk-django-poll-index-view.py>>
<<def-django-tut-poll-detail.py>>
<<def-django-tut-poll-results.py>>
<<def-django-tut-poll-vote.py>>
#+end_SRC
#+NAME: blk-django-tut-urlpatterns-urls.py
#+BEGIN_SRC python
urlpatterns = [
path('', views.index, {"arg1": "hello"}, name='index'),
# ex: /polls/5/
path('<int:question_id>/', views.detail, name='detail'),
# ex: /polls/5/results/
path('<int:question_id>/results/', views.results, name='results'),
# ex: /polls/5/vote/
path('<int:question_id>/vote/', views.vote, name='vote'),
]
#+end_SRC
#+NAME: blk-django-tut-patterns-urls.py
#+CAPTION: Use a foreing key to activate a view (Django appends "_id" to the foreign key field name; =question= is the foreign key of =Choice=)
#+begin_SRC python :tangle media/django/mytut/polls/urls.py
from django.urls import path
from . import views
<<blk-django-tut-urlpatterns-urls.py>>
mkdir templates polls/templates
mkdir templates polls/templates/polls
polls/templates/polls/index.html¶ {% if latest_question_list %}
No polls are available.
{% endif %}def index(request, **kwargs): latest_question_list = Question.objects.order_by('-pub_date')[:5] template = loader.get_template('polls/index.html') context = { 'latest_question_list': latest_question_list, } return HttpResponse(template.render(context, request))
from django.http import HttpResponse from django.template import loader
from .models import Question
<>
<>
<>
<>
def detail(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/detail.html', {'question': question})
from django.http import HttpResponse from django.template import loader from django.shortcuts import get_object_or_404, render
from .models import Question
<>
<>
<>
<>
The way this works is by looking up the URL definition as specified in the polls.urls module. You can see exactly where the URL name of ‘detail’ is defined below:
... # the 'name' value as called by the {% url %} template tag path('/', views.detail, name='detail'), ...
If you want to change the URL of the polls detail view to something else, perhaps to something like polls/specifics/12/ instead of doing it in the template (or templates) you would change it in polls/urls.py:
...
path('specifics//', views.detail, name='detail'), ...
from django.urls import path from . import views
app_name = 'polls' <>
{% if latest_question_list %}
No polls are available.
{% endif %}The tutorial says
When you’re comfortable with writing views, read part 4 of this tutorial
I am going to try to create something.
For a view, I need an existing model (schema; table). In this case, I will use Question
. The views for polls
are defined in mytut/polls/views.py
def test(request, testarg): myquestion = get_list_or_404( Question, question_text__startswith=testarg) reqstr = "This is the request {0}".format(request) print(reqstr) myqstr = "This is the list of questions\n{0}".format( myquestion) print(myqstr) return render(request, 'polls/test.html', {'mytext': "{0}\n{1}".format(reqstr, myqstr), 'mylist': (reqstr, myqstr)})
from django.http import HttpResponse from django.template import loader from django.shortcuts import get_object_or_404, get_list_or_404, render
from .models import Question
<>
<>
<>
<>
<>
mytext
({{ mytext }}
)) can be keyword argument (of the dictionary of render
in the definition of the test
view)
{% for mysubtext in mylist %}
{{ mysubtext }}
{% endfor %}
Note how the \n makes no difference here.
{{ mytext|linebreaksbr }}
Note how the \n does makes a difference here, as compared to the previous case {% else %}No text :).
{% endif %} #+end_SRCpolls:testing
)app_name = 'polls' <> urlpatterns += [path('mytest/', views.test, name='testing')] #+END_SRC
urls.py
) receives the pattern from the typed-in url (browser) and finds the appropriate function to execute in the views.py
. This also defines the name, in case that we want to use a template built-in ({% url %}
, e.g. for a link)views.py
is executed, and the returned value is passed to the HTML template (test.html
)test.html
) replaces Django's built-in code, which can include Python-like primitives{% if error_message %}
{{ error_message }}
{% endif %}
{% csrf_token %}
{% for choice in question.choice_set.all %}
{{ choice.choice_text }}
{% endfor %}
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
selected_choice = question.choice_set.get(
pk=request.POST['mychoice'])
except (KeyError, Choice.DoesNotExist):
# Redisplay the question voting form.
return render(request, 'polls/detail.html', {
'question': question,
'error_message': "You didn't select a choice.",
})
else:
selected_choice.votes += 1
selected_choice.save()
# Always return an HttpResponseRedirect after successfully dealing
# with POST data. This prevents data from being posted twice if a
# user hits the Back button.
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
#+end_SRC
#+NAME: def-django-tut-poll-choice-vote.py
#+BEGIN_SRC python
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
selected_choice = question.choice_set.get(
pk=request.POST['mychoice'])
selected_choice.votes =
except (KeyError, Choice.DoesNotExist):
# Redisplay the question voting form.
return render(request, 'polls/detail.html', {
'question': question,
'error_message': "You didn't select a choice.",
})
else:
# Avoid race condition with F (let the database
# process the modification)
selected_choice.votes = F('votes') + 1
selected_choice.save()
# Always return an HttpResponseRedirect after successfully dealing
# with POST data. This prevents data from being posted twice if a
# user hits the Back button.
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
#+end_SRC
#+NAME: def-django-tut-poll-choice-results.py
#+CAPTION:
#+BEGIN_SRC python :results none
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})
from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, get_list_or_404, render from django.urls import reverse from django.db.models import F
from .models import Choice, Question
<>
<>
<>
<>
<>
Vote again?
app_name= "polls" urlpatterns = [ path('', views.IndexView.as_view(), name='index'), path('/', views.DetailView.as_view(), name='detail'), path('/results/', views.ResultsView.as_view(), name='results'), path('/vote/', views.vote, name='vote'), ]
from django.urls import path from . import views
<>
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
"""Return the last five published questions."""
return Question.objects.order_by('-pub_date')[:5]
class DetailView(generic.DetailView):
model = Question
template_name = 'polls/detail.html'
class ResultsView(generic.DetailView):
model = Question
template_name = 'polls/results.html'
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic
from .models import Choice, Question
<<def-django-tut-poll-genericviews-index.py>>
<<def-django-tut-poll-genericviews-detail.py>>
<<def-django-tut-poll-genericviews-results.py>>
<<def-django-tut-poll-choice-vote.py>>
<<def-django-tut-mytest-view.py>>
import datetime
from django.utils import timezone from polls.models import Question
# create a Question instance with pub_date 30 days in the future future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30)) # was it published recently? print(future_question.was_published_recently())
True
def test_was_published_recently_with_future_question(self):
"""
was_published_recently() returns False for questions whose pub_date
is in the future.
"""
time = timezone.now() + datetime.timedelta(days=30)
future_question = Question(pub_date=time)
self.assertIs(future_question.was_published_recently(), False)
import datetime
from django.test import TestCase
from django.utils import timezone
from .models import Question
class QuestionModelTests(TestCase):
<<def-test_was_published_recently_with_future_question.py>>
python manage.py test polls 2>&1 || echo
Creating test database for alias 'default'... F ====================================================================== FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests) was_published_recently() returns False for questions whose pub_date ---------------------------------------------------------------------- Traceback (most recent call last): File "media/django/mytut/polls/tests.py", line 18, in test_was_published_recently_with_future_question self.assertIs(future_question.was_published_recently(), False) AssertionError: True is not False
---------------------------------------------------------------------- Ran 1 test in 0.001s
FAILED (failures=1) Destroying test database for alias 'default'... System check identified no issues (0 silenced).
from django.db import models
# Create your models here. class Question(models.Model): question_text = models.CharField(max_length=200) <> def __str__(self): return self.question_text
def was_published_recently(self): now = timezone.now() delta = now - datetime.timedelta(days=1) return delta <= self.pub_date <= now
class Choice(models.Model): <> <> <> def __str__(self): return self.choice_text
def test_was_published_recently_with_old_question(self):
"""
was_published_recently() returns False for questions whose pub_date
is older than 1 day.
"""
time = timezone.now() - datetime.timedelta(days=1, seconds=1)
old_question = Question(pub_date=time)
self.assertIs(old_question.was_published_recently(), False)
def test_was_published_recently_with_recent_question(self):
"""
was_published_recently() returns True for questions whose pub_date
is within the last day.
"""
time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59)
recent_question = Question(pub_date=time)
self.assertIs(recent_question.was_published_recently(), True)
import datetime
from django.test import TestCase
from django.utils import timezone
from .models import Question
class QuestionModelTests(TestCase):
<<def-test_was_published_recently_with_future_question.py>>
<<def-multi-django-tests.py>>
file:///usr/share/doc/django/html/intro/tutorial05.html#a-test-for-a-view
from django.test.utils import setup_test_environment setup_test_environment()
from django.test import Client # create an instance of the client for our use client = Client()
response = client.get('/')
Not Found: /
print(response.status_code)
404
from django.urls import reverse response = client.get(reverse('polls:index')) print(response.status_code)
200
print(response.content)
b'\n
print(response.context['latest_question_list'])
]>
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
"""Return the last five published questions
(avoiding those set in the future).
"""
return Question.objects.filter(
pub_date__lte=timezone.now()
).order_by('-pub_date')[:5]
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic
from django.utils import timezone
from .models import Choice, Question
<<def-django-tut-poll-testclient-index.py>>
<<def-django-tut-poll-genericviews-detail.py>>
<<def-django-tut-poll-genericviews-results.py>>
<<def-django-tut-poll-choice-vote.py>>
<<def-django-tut-mytest-view.py>>
def create_question(question_text, days): """Create a question with the given `question_text` and published the given number of `days` offset to now (negative for questions published in the past, positive for questions that have yet to be published).
""" time = timezone.now() + datetime.timedelta(days=days) return Question.objects.create( question_text=question_text, pub_date=time)
class QuestionIndexViewTests(TestCase): def test_no_questions(self): """If no questions exist, an appropriate message is displayed. """ response = self.client.get(reverse('polls:index')) # Assert empty response from server self.assertEqual(response.status_code, 200) # Assert empty msg self.assertContains(response, "No polls are available.") self.assertQuerysetEqual( response.context['latest_question_list'], [])
def test_past_question(self): """Questions with a pub_date in the past are displayed on the index page. """ create_question(question_text="Past question.", days=-30) response = self.client.get(reverse('polls:index')) self.assertQuerysetEqual( response.context['latest_question_list'], [''] )
def test_future_question(self): """Questions with a pub_date in the future aren't displayed on the index page. """ create_question(question_text="Future question.", days=30) response = self.client.get(reverse('polls:index')) self.assertContains( response, "No polls are available.") self.assertQuerysetEqual( response.context['latest_question_list'], [])
def test_future_question_and_past_question(self): """Even if both past and future questions exist, only past questions are displayed. """ create_question(question_text="Past question.", days=-30) create_question(question_text="Future question.", days=30) response = self.client.get(reverse('polls:index')) self.assertQuerysetEqual( response.context['latest_question_list'], [''] )
def test_two_past_questions(self): """The questions index page may display multiple questions. """ create_question(question_text="Past question 1.", days=-30) create_question(question_text="Past question 2.", days=-5) response = self.client.get(reverse('polls:index')) self.assertQuerysetEqual( response.context['latest_question_list'], ['', ''] )
import datetime
from django.test import TestCase
from django.utils import timezone
from django.urls import reverse
from .models import Question
class QuestionModelTests(TestCase):
<<def-test_was_published_recently_with_future_question.py>>
<<def-multi-django-tests.py>>
<<blk-django-tut-testclient-indexview-test.py>>
<<blk-django-tut-testclient-detailview-test.py>>
class DetailView(generic.DetailView):
model = Question
template_name = 'polls/detail.html'
def get_queryset(self):
"""
Excludes any questions that aren't published yet.
"""
return Question.objects.filter(pub_date__lte=timezone.now())
class QuestionDetailViewTests(TestCase): def test_future_question(self): """The detail view of a question with a pub_date in the future returns a 404 not found. """ future_question = create_question( question_text='Future question.', days=5) url = reverse('polls:detail', args=(future_question.id,)) response = self.client.get(url) self.assertEqual(response.status_code, 404)
def test_past_question(self): """The detail view of a question with a pub_date in the past displays the question's text. """ past_question = create_question( question_text='Past Question.', days=-5) url = reverse('polls:detail', args=(past_question.id,)) response = self.client.get(url) self.assertContains(response, past_question.question_text)
mkdir polls/static
mkdir -p polls/static/polls
li a { color: green; }
body { background: white url("icon-cashier.svg") no-repeat; }
{% load static %}
<>
from django.contrib import admin
# Register your models here.
from .models import Question
class QuestionAdmin(admin.ModelAdmin): fields = ['pub_date', 'question_text']
admin.site.register(Question, QuestionAdmin)
from django.contrib import admin
# Register your models here.
from .models import Question
class QuestionAdmin(admin.ModelAdmin): fieldsets = [ (None, {'fields': ['question_text']}), ('Date information', {'fields': ['pub_date']}), ]
admin.site.register(Question, QuestionAdmin)
from .models import Choice <> admin.site.register(Choice)
from django.contrib import admin
from .models import Choice, Question
class ChoiceInline(admin.StackedInline): model = Choice extra = 3
class QuestionAdmin(admin.ModelAdmin): fieldsets = [ (None, {'fields': ['question_text']}), ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}), ] inlines = [ChoiceInline]
admin.site.register(Question, QuestionAdmin)
:LOGBOOK: CLOCK: [2020-08-31 lun 20:10]--[2020-08-31 lun 20:24] => 0:14 CLOCK: [2020-08-31 lun 18:45]--[2020-08-31 lun 20:06] => 1:21 :END:
The communication between pretix and the plugins happens mostly using Django's `signal dispatcher`_ feature. The core modules of pretix, ``pretix.base``, ``pretix.control`` and ``pretix.presale`` expose a number of signals
There are two ways you can connect a receiver to a signal. You can take the manual connect route:
from django.core.signals import request_finished
def my_callback(sender, **kwargs): print("Request finished!")
request_finished.connect(my_callback)
Alternatively, you can use a receiver() decorator: ... Here’s how you connect with the decorator:
from django.core.signals import request_finished from django.dispatch import receiver
@receiver(request_finished) def my_callback(sender, **kwargs): print("Request finished!")
Now, our my_callback function will be called each time a request finishes.
Where should this code live?
Strictly speaking, signal handling and registration code can live anywhere you like, although it’s recommended to avoid the application’s root module and its models module to minimize side-effects of importing code.
mkdir -p media/django/mytut/polls/signals/polls/
In practice, signal handlers are usually defined in a signals submodule of the application they relate to. Signal receivers are connected in the ready() method of your application configuration class
class RockNRollConfig(AppConfig):
name = 'rock_n_roll'
verbose_name = "Rock ’n’ roll"
#+end_src
#+begin_quote
You can make your application load this AppConfig
subclass by default as follows:
#+end_quote
#+CAPTION: rock_n_roll/__init__.py
#+begin_src python
default_app_config = 'rock_n_roll.apps.RockNRollConfig'
#+end_src
#+begin_quote
That will cause RockNRollConfig
to be used when INSTALLED_APPS
contains 'rock_n_roll'
. This allows you to make use of AppConfig
features without requiring your users to update their INSTALLED_APPS
setting.
...
The recommended convention is to put the configuration class in a submodule of the application called apps
. However, this isn’t enforced by Django.
...
If your code imports the application registry in an application's __init__.py
, the name apps
will clash with the apps
submodule.
#+end_quote
Subclasses can override this method to perform initialization tasks such as registering signals. It is called as soon as the registry is fully populated.
Example: #+end_quote #+begin_src python from django.apps import AppConfig from django.db.models.signals import pre_save
class RockNRollConfig(AppConfig): # ...
def ready(self): # importing model classes from .models import MyModel # or... MyModel = self.get_model('MyModel')
# registering signals with the model's string label pre_save.connect(receiver, sender='app_label.MyModel') #+end_src
from django.apps import AppConfig
class PollsConfig(AppConfig): name = 'polls' verbose_name = "My tutorial poll application" def ready(self): import polls.signals.polls.callbacks
(see also Using signals for denormalizing counts (2020). In Antonio Melé; Django 3 By Example: Build powerful and reliable Python web applications from scratch. Packt Publishing Ltd, pp.210)
you’ll only be interested in receiving a certain subset of those signals ... Most of the time, you don’t need to know when any model gets saved -- just when one specific model is saved. ... you can register to receive signals sent only by particular senders
from django.core.signals import request_finished from django.db.models.signals import pre_save from django.dispatch import receiver from polls.models import Question
@receiver(pre_save, sender=Question) def my_handler(sender, **kwargs): print("Question request done!")
def my_callback(sender, **kwargs): print("Request finished!")
request_finished.connect(my_callback, dispatch_uid="1d61c14e")
dispatch_uid
is supposed to be so "that your receiver function will only be bound to the signal once for each unique dispatch_uid value" (whatever that is, but Celery seems to like it, and it seems to be related to calls like e-mail too)
Once Pretix is installed, it needs to be configured (teams, and such).
admin
useradmin@localhost
is admin)#+caption: Landing page for Pretix
admin@locahost
and use admin as the password#+caption: Using admin user to sign in file:media/pretix-admin-login.png
#+caption: Initial screen when signing up as admin for the first time file:media/pretix-admin-ini-dashboard.png
#+caption: Go into administratative session with the admin user
#+caption: Initial screen as admin in administrative mode file:media/pretix-admin-admin_mode.png
#+caption: Going into organizers as /admin/ file:media/pretix-admin-admin_mode--orgnanizers-mark.svg
See what can be done with Pretix as it is
Every plugin has to be implemented as an independent Django 'app' living in its own python package installed like any other python module. ... The communication between pretix and the plugins happens mostly using Django's `signal dispatcher`_ feature. The core modules of pretix, ``pretix.base``, ``pretix.control`` and ``pretix.presale`` expose a number of signals
I think that I am ready to start playing. I am not a master of Django signals (Learn Django and Signals), but I think that should suffice to start hacking.
Before I can create a , I need to
Please read `Creating a plugin ` first, if you haven't already.
The core modules of pretix, ``pretix.base``, ``pretix.control`` and ``pretix.presale`` expose a number of signals which are documented on the next pages
torsocks git clone https://github.com/pretix/pretix-plugin-cookiecutter.git
This software may only be used or distributed under this paid license. +{% elif cookiecutter.license == "GPLv3" %} +All the software here is free software: you can redistribute +it and/or modify it under the terms of the GNU General +Public License as published by the Free Software Foundation, +version 3 of the License. + +This program is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied +warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU +General Public License along with this program. +If not, see . + {% endif %}
cookiecutter pretix-plugin-cookiecutter/
repo_name [pretix-superplugin]: taprelix repo_url [GitHub repository URL]: https://notabug.org/broncodev/taprelix.git module_name [pretix_superplugin]: pretix_taprelix human_name [Superplugin]: Taprelix author_name [Your name]: eDgar author_email [Your email]: year [Current year]: 2020 short_description [Short description]: A Taler-Pretix interface Select license: 1 - Apache 2 - pretix Enterprise 3 - GPLv3 Choose from 1, 2, 3 [1]: 3 min_basever [2.7.0]: Select category: 1 - FEATURE 2 - PAYMENT 3 - INTEGRATION 4 - CUSTOMIZATION 5 - FORMAT 6 - API Choose from 1, 2, 3, 4, 5, 6 [1]: 2
The above cookiecutter
creates the code indicated in the
from django.utils.translation import gettext_lazy
try: from pretix.base.plugins import PluginConfig except ImportError: raise RuntimeError("Please use pretix 2.7 or above to run this plugin!")
version = '1.0.0'
<>
class PretixPluginMeta: name = gettext_lazy('Taprelix') author = 'eDgar' description = gettext_lazy('A Taler-Pretix interface') visible = True version = __version__ category = 'PAYMENT' compatibility = "pretix>=2.7.0"
def ready(self): from . import signals # NOQA
default_app_config = 'pretix_taprelix.PluginApp'
Sill, some things need to be changed. In the documentation one sees
class PaypalApp(PluginConfig): name = 'pretix_paypal' verbose_name = _("PayPal")
but the application created with the cookie cutter (Blk. blk-pretix-cookiecutter-taprelix-init.py) shows (=PluginApp=; see).
class PluginApp(PluginConfig): name = 'pretix_taprelix' verbose_name = 'Taprelix'
Django's documentation also indicates that this should be (in our case) TaprelixApp
(Blk. blk-pretix-cookiecutter-needs-change.py):
class TaprelixApp(PluginConfig): name = 'pretix_taprelix' verbose_name = 'Taprelix'
Note that this is not really like my_django_app/apps.py
(Blk. blk-django-signaldoc-apps.py). Meaning that it is located directy in the __init__.py
of the Django app's root directory.
During development, you can just run
sudo -u pretix source /var/pretix/venv/bin/activate sudo -u pretix python -m pretix migrate sudo -u pretix python -m pretix rebuild sudo -u pretix python -m pretix updatestyles <>
rc-service pretix-load restart
,* Stopping Pretix web service through gunicorn ... [ ok ] ,* Stopping pretix-worker ... [ ok ] ,* Stopping postfix ... [ ok ] ,* Stopping redis ... [ ok ] ,* Stopping nginx ... [ ok ] ,* Stopping PostgreSQL (this can take up to 92 seconds) ... * RUDE_QUIT enabled. [ ok ] ,* /run/postgresql: correcting mode ,* Starting PostgreSQL ... [ ok ] ,* Checking nginx' configuration ... [ ok ] ,* Starting nginx ... [ ok ] ,* Starting redis ... [ ok ] ,* Starting postfix ... [ ok ] ,* Starting pretix-worker ... [ ok ] ,* Starting Pretix web service through gunicorn ... [ ok ]
chown :"$USER" -R var/pretix/venv/lib/python3.8/site-packages/ chmod -R g+w site-packages
source /var/pretix/venv/bin/activate sudo -u pretix torsocks pip3 install -U "git+https://github.com/pretix/pretix.git#egg=pretix&subdirectory=src" python setup.py develop
['./taprelix', '/usr/lib/python38.zip', '/usr/lib/python3.8', '/usr/lib/python3.8/lib-dynload', '/var/pretix/venv/lib/python3.8/site-packages', './taprelix', './taprelix/', './taprelix/pretix_taprelix'] running develop running egg_info writing taprelix.egg-info/PKG-INFO writing dependency_links to taprelix.egg-info/dependency_links.txt writing entry points to taprelix.egg-info/entry_points.txt writing top-level names to taprelix.egg-info/top_level.txt reading manifest file 'taprelix.egg-info/SOURCES.txt' reading manifest template 'MANIFEST.in' writing manifest file 'taprelix.egg-info/SOURCES.txt' running build_ext Creating /var/pretix/venv/lib/python3.8/site-packages/taprelix.egg-link (link to .) taprelix 0.0.1 is already the active version in easy-install.pth
Installed taprelix Processing dependencies for taprelix==0.0.1 Finished processing dependencies for taprelix==0.0.1
I basically copied Pretix' Paypal
™ and replaced Paypal ™ with Taler
<>
apps.py
) works:LOGBOOK: CLOCK: [2020-09-29 mar 20:50]--[2020-09-29 mar 21:45] => 0:55 CLOCK: [2020-09-17 jue 16:17]--[2020-09-17 jue 16:57] => 0:40 CLOCK: [2020-09-15 mar 12:55]--[2020-09-15 mar 13:21] => 0:26 CLOCK: [2020-09-13 dom 20:16]--[2020-09-13 dom 20:43] => 0:27 CLOCK: [2020-09-12 sáb 12:13]--[2020-09-12 sáb 12:19] => 0:06 CLOCK: [2020-09-08 mar 20:40]--[2020-09-08 mar 20:43] => 0:03 CLOCK: [2020-09-08 mar 19:59]--[2020-09-08 mar 20:32] => 0:33 :END:
There is a PayPal™ plugin which seems to be similar to what we need. I already copied that tree , and updated the database. It is now time to see how to really connect Taler with Pretix.
rc-service taler-server start
,* /run/postgresql: creating directory ,* /run/postgresql: correcting mode ,* /run/postgresql: correcting owner ,* Starting PostgreSQL ... [ ok ] ,* Checking nginx' configuration ... [ ok ] ,* Starting nginx ... [ ok ] ,* Starting redis ... [ ok ] ,* Starting postfix ... [ ok ] ,* Starting pretix-worker ... [ ok ] ,* Starting Pretix web service through gunicorn ... [ ok ] ,* Starting Taler Service Daemon ... [ ok ]
Basically, the plugin needs to do this (see )
(handle error and go back; how?)
----------------------------------------+
| |
v |
/--------\ +-------- ----------- -+N |
| Pretix +--->| sell a +--------->| create an +----->|?|----+
\-------- | ticket | (plugin | HTTP req. | -/
^ +-------- starts ----------- |(Order
| here) (JSON for | `POST'ed
|(Let Pretix Taler | successfully)
| handle the rest) order) |
| v
--+--- --------+
|Ticket| +order id| (contract
| sold | +-------- on Taler)
------ |instance|
^ ----+---+
| |
| (paid) v
| /- -------------- -------------- --------------+
+--|?|<--- plugin |<--+ user pays at |<--+ plugin shows |
-/ |/check-payment| |/check-payment| |/check-payment|
N| +-------------- -------------- --------------+
| ^
| |
+--------------------------------------------------
python setup.py develop
_
with gettext_lazy
pretix.plugins.paypal
→ pretix.plugins.taler
From the conversation with Christian Grothoff and , we can create a minimal order like this
curl -H "Content-Type: application/json" -X POST -d '{"order":{"amount":"KUDOS:2","summary":"The summary","fulfillment_url":"https://example.com/"}}' http://localhost:8888/private/orders
It seems that the magic happens in
SUPPORTED_CURRENCIES = ['KUDOS']
# LOCAL_ONLY_CURRENCIES = ['INR']
# ...
# if self.event.currency in LOCAL_ONLY_CURRENCIES:
# settings_content += '<br><br><div class="alert alert-warning">%s''</div>' % (
# gtlz("Your event's currency is supported by PayPal as a payment and balance currency for in-country "
# "accounts only. This means, that the receiving as well as the sending PayPal account must have been "
# "created in the same country and use the same currency. Out of country accounts will not be able to "
# "send any payments.")
# )
:LOGBOOK: CLOCK: [2020-09-12 sáb 13:44]--[2020-09-12 sáb 14:04] => 0:20 CLOCK: [2020-09-06 dom 18:17]--[2020-09-06 dom 18:20] => 0:03 CLOCK: [2020-09-01 mar 20:13]--[2020-09-01 mar 20:34] => 0:21 CLOCK: [2020-08-31 lun 21:31]--[2020-08-31 lun 21:41] => 0:10 CLOCK: [2020-08-28 vie 21:48]--[2020-08-28 vie 21:50] => 0:02 CLOCK: [2020-08-07 vie 21:36]--[2020-08-07 vie 21:46] => 0:10 CLOCK: [2020-08-05 mié 21:54]--[2020-08-05 mié 22:00] => 0:03 CLOCK: [2020-08-02 dom 20:21]--[2020-08-02 dom 20:30] => 0:09 CLOCK: [2020-08-01 sáb 22:00]--[2020-08-01 sáb 22:18] => 0:18 :END:
:LOGBOOK: CLOCK: [2020-08-15 sáb 20:33]--[2020-08-15 sáb 20:47] => 0:14 :END:
CLOSED: [2020-08-02 dom 17:12]
After reading this 2 or 3 times in :
It is possible for the merchant to refund a contract order, for example if the contract cannot be fulfilled after all. Refunds are only possible until the exchange has wired the payment to the merchant. Once the funds have been wired, refunds are no longer allowed by the Taler exchange.
I think that it should be changed: wired seems to be the process of transferring money, but in this context, it is used as the money being sent from the customer to the merchant. The explanation is contradictory, because--under that understanding--/wired/ can only go from the customer to the merchant (which is very unlikely), and it only makes sense that a refund can happen after the money was wired to the merchant (contrary to what the text says).
I think that the intention is to say that a refund can no longer happen once it has been already given (at least that is how I understood it after a while):
Once a refund has been wired, it is no longer allowed by the Taler exchange.
The text is correct, but I've clarified it a bit:
""Refunds are only possible after the customer paid and before the exchange has wired the payment to the merchant.""
We always use the term "wired' for bank transfers, never for Taler payments. So once the merchant has received the money from the *exchange*, the exchange will never give it back to customers.
CLOSED: [2020-08-02 dom 17:13]
Since tips are not taxable, but some people donate to deduct taxes, is there a need to have taxable tips? or how is that managed with Taler?
Tips are assumed to be small, non-contractual gifts where no taxes apply. After all, the user cannot force a Web site to give a tip. So yes, you could say that tips are a form of non-taxable income. We don't expect this to be an issue in practice, but of course governments could outlaw the practice.
taler/*
[2020-07-30 jue]CLOSED: [2020-08-02 dom 17:13]
src/testing/testing_api_cmd_rewind.c
had this:
#include #include
but there is no include/taler/
directory. I was able to compile once I did
# Find files which have `include
./include/taler_twister_testing_lib.h ./testing/test_bank_api_twisted.c ./testing/testing_api_cmd_twister_exec_client.c ./testing/test_exchange_api_twisted.c ./testing/testing_api_trait_uuid.c
find
... ::-type f
\( -name '*.c' -o -name '*.h' \)
include <taler/
taler/
s
..., is used to replace strings with regular expressions. The first character is used to delimit the start and end of the string to be replaced and the end of the string used to replace. g
is used to prevent propagation of case conversion (see info sed
)\(...\)
\1
, \2
, ..., \9
Well, that's a bit too sledgehammer, but you are right about the original report that several of the #include's included taler/ where they should not have. I have fixed those now.
Thanks for reporting!
> ---------------------------------------- > From: Christian Grothoff > Sent: Sun Aug 02 19:17:45 CEST 2020 > To: Edgar Lux > Subject: Re: Job from FSF > [...] > The text is correct, but I've clarified it a bit: > [...]
Oh! That's it! Thanks!
> [...] > but of course governments could > outlaw the practice.
HAHAHA Funny! I meant more like the donations that The Tor Project gets, which are deductible. I kept thinking about it, and I guess that a Taler contract can be generated for a donation of this kind, if needed.
> Well, that's a bit too sledgehammer > [...]
Great! (in my "defense", I only did it when I realised that there were only few--after the first 2). I'm glad that it worked :) !
CLOSED: [2020-08-02 dom 20:19]
After trying to connect
<>
I am getting this:
[...] < Access-Control-Allow-Origin: * < Strict-Transport-Security: max-age=63072000; includeSubDomains; preload < X-XSS-Protection: 1; mode=block < X-Frame-Options: SAMEORIGIN < X-Content-Type-Options: nosniff < Content-Security-Policy: default-src 'self' https://www.taler.net/dist/; img-src 'self' https://secure.gravatar.com/ https://git.taler.net/ data: blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.taler.net/dist/js/; style-src 'self' 'unsafe-inline' https://www.taler.net/dist/css/; connect-src 'self' wss://buildbot.taler.net; object-src 'self' blob: < Referrer-Policy: same-origin
Does it mean that the server is blocking me?
You omitted the:
HTTP/1.1 204 No Content
which simply means: the operation was successful, alas there is nothing else to report, so you are not getting a body. So no, your request simply worked.
Run:
$ curl -H "Authorization: ApiKey sandbox" \ http://backend.test.taler.net/private/instances
to see that now a 'lux' instance exists!
Duh! Thanks
Hi, I was happy to see that I configured my backend correctly.
I think that we need to move this:
Please note that your backend is right now likely globally reachable. Production systems should be configured to bind to a UNIX domain socket and use TLS for improved network privacy, see Secure setup <Secure-setup>
.
just before the command to start the backend (taler-merchant-httpd
) in taler-merchant-manual.rst. One could say that the warning is at further above, but it took me by surprise, and others may have the same.
CLOSED: [2020-08-02 dom 17:13] :LOGBOOK: CLOCK: [2020-08-02 dom 09:18]--[2020-08-02 dom 09:41] => 0:23 :END:
The frontend accesses the backend via a RESTful API
By separating the user interface concerns from the data storage concerns, we improve the portability of the user interface across multiple platforms and improve scalability by simplifying the server components.
Each request from client to server must contain all of the information necessary to understand the request, and cannot take advantage of any stored context on the server. Session state is therefore kept entirely on the client.
Cache constraints require that the data within a response to a request be implicitly or explicitly labeled as cacheable or non-cacheable. If a response is cacheable, then a client cache is given the right to reuse that response data for later, equivalent requests.
The layered system style allows an architecture to be composed of hierarchical layers by constraining component behavior such that each component cannot "see" beyond the immediate layer with which they are interacting.
REST allows client functionality to be extended by downloading and executing code in the form of applets or scripts. This simplifies clients by reducing the number of features required to be pre-implemented. #+end_QUOTE
#+begin_QUOTE ...Any information that can be named can be a *resource*: a document or image, a temporal service, a collection of other resources, a non-virtual object (e.g. a person), and so on... #+end_QUOTE
#+begin_QUOTE ...The state of the resource at any particular timestamp is known as resource representation ... consists of data, metadata describing the data and hypermedia links which can help the clients in transition to the next desired state. #+end_QUOTE
#+begin_QUOTE ...Every addressable unit of information carries an address, either explicitly (e.g., link and id attributes) or implicitly (e.g., derived from the media type definition and representation structure). #+end_QUOTE
#+begin_QUOTE Hypertext (or hypermedia) means the simultaneous presentation of information and controls such that ... the user (or automaton) obtains choices and selects actions ... Machines can follow links when they understand the data format and relationship types. #+end_QUOTE
#+begin_QUOTE ...resource representations shall be self-descriptive: the client does not need to know if a resource is employee or device. It should act on the basis of media-type associated with the resource. So in practice, you will end up creating lots of custom media-types -- normally one media-type associated with one resource. #+end_QUOTE
#+begin_QUOTE Resources are decoupled from their representation so that their content can be accessed in a variety of formats ... Metadata about the resource is available and used ... every interaction with a resource is stateless.
I know that for the purpose of this project, I don't really need a local backend, but I think that it can be useful (I started to set it up when I thought that the web server was not letting me in).
I am getting this:
curl: (7) Failed to connect to localhost port 8888: Connection refused
I am obsiously not using that port. My firewall is also not restricting it, and I don't have any iptables. What am I doing wrong?
The relevant portion of my configuration is outlined here:
<>
<>
Thanks!
You've configured to bind your merchant backend to a UNIX domain socket and not a tcp port.
Use:
taler-config -s MERCHANT -o SERVE -V TCP
and check with 'netstat -ntpl' that the merchant is actually listening on 8888.
Happy hacking!
Christian
According to the documentation, setting the TCP option
Please note that your backend is right now likely globally reachable. -- taler-merchant-manual.rst
I am trying to prevent this. That is why I wanted the UNIX socks, but something tells me that it will require more work. I thought that I would only need my PostgreSQL database and a working directory.
On 8/2/20 10:58 PM, Edgar Lux wrote: > According to the documentation, setting the TCP option > > Please note that your backend is right now likely > globally reachable. – taler-merchant-manual.rst
Indeed.
> I am trying to prevent this. That is why I wanted the UNIX > socks, but something tells me that it will require more > work.
Indeed. Alas, the simplest solution would be to block the port using iptables (host-based firewall).
> I thought that I would only need my PostgreSQL > database and a working directory.
Well, depends on what you want to do, that might indeed do. But right now we don't (yet) have an option to bind to localhost, and your curl/wget/browser-clients can't directly talk to the UNIX domain socket. So if you don't want the service on the Internet, something has to give...
> Indeed. Alas, the simplest solution would be to block the port using > iptables (host-based firewall).
I can do that.
By the way, I am already using python-requests
to interface with the web backend! I also already installed mumble (and murmur).
It seems that Pretix is going to require some time to be configured (SMTP, etc.). If I go mute, it's because I'm working on setting it up.
In the mean time, I send some questions
CLOSED: [2020-08-09 dom 00:09]
Hi. There is a section called Accepting a Simple Payment in the taler-merchant-api-tutorial.rst
which shows how to place and check an order, but not so much how to accept it (if the user were the bank or the merchant).
I've changed it to 'Merchant Payment Processing'. May be better, not sure.
CLOSED: [2020-08-09 dom 00:09]
I'm not trying to hijack the purpose of this project. I am in fact very glad to have the chance to learn, but there is a framework (some would call it an ERP) called Tryton which is written in Python. I obviously don't know the motivation behind using Pretix (nor do i need to know; I'm really not asking), but by the looks of it, it is just a ticket system, and the point is to use a real-life application with Taler. Tryton could also be connected to-- and used to test Taler (e.g. I think that there are modules for Stripe).
Tryton would also be a good (future) target, maybe even good as the next target. I picked Pretix because it is used by https://www.ccc.de/ for their event-management/ticketing purposes, and as we will likely first launch in Germany, and as German hackers are a good first target audience, we figured it would be good to support their ticketing solution that they use for their events.
Given that we know plenty of CCC-people, it seems likely that they will actually put it into production once everything is ready. I don't know any Tryton users myself, so I don't know whom to talk to for a real-world deployment.
That said, let's see if we can get Pretix to work, and if it does, we can discuss doing Tryton next ;-).
> I picked Pretix because it is used by https://www.ccc.de/
Me: groupie!
> Tryton users myself, so I don't know whom to talk to for a real-world deployment.
I have deployed it before, but I'm not running any business right now. I'm sure that they would be interested.
> let's see if we can get Pretix to work
Yes, indeed!
> Having contacts with Japanese, this cultural phenomenon is appreciated.
Really, do Japanese believe it so? Confucian or Taoist tradition, it must be.
CLOSED: [2020-09-01 mar 20:38]
Guten tag, konichiwa, hola Christian.
It seems that the e-mail service is not working in my system. This seems to be very important for Pretix, because an event can only be created by an organizer, who needs to invite members by e-mail. I am using a local server for everything.
I am going to try to get a hold of the Pretix. If you know what I am doing wrong by looking into the Install Pretix section or even directly from Final configuration files), please, let me know. By the way, I unplugged my Internet and turned off my firewall to test.
I am what I got in my e-mail client (see the Emacs config).
Thanks!
I don't recall pretix needing e-mail, but then again, I do usually run at least a minimal Postfix setup on servers...
Looks like you are simply missing Redis (cache, local service) in your setup. I don't recall setting up Redis last time I played with Pretix, but it has been a while, and maybe it was already present...
Happy hacking!
Christian
Oh, check what
$ netstat -ntpl
says.
You should have a 'redis' serice listen on port 6379. If that is not the case, then this is your first problem.
-Christian
netstat -ntpl
Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN - tcp6 0 0 :::111 :::* LISTEN -
rc-service pretix-load start
netstat -ntpl
Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 127.0.0.1:6379 0.0.0.0:* LISTEN - tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN - tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN - tcp 0 0 127.0.0.1:5432 0.0.0.0:* LISTEN - tcp 0 0 127.0.0.1:8345 0.0.0.0:* LISTEN - tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN - tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN - tcp6 0 0 :::111 :::* LISTEN - tcp6 0 0 :::80 :::* LISTEN - tcp6 0 0 :::443 :::* LISTEN -
I think that I will try to continue without e-mail. If you were able to work with Pretix without it, I should be able to do the same.
I tested my ports, and they are listening. I can also send and receive e-mails to myself without Pretix $ mail -s "Hello world 1" $USER@localhost
It would be nice to know what is happening, but I think that I will go for the bigger goal of looking at the API. The good news is that I think that I found where I can start: https://docs.pretix.eu/en/latest/development/api/payment.html
Tschüss!
It seems that the e-mail bug has been expelled. As I was working on the Pretix plugin for Taler, the pretix-worker
service shown to be the same as pretix-web
. Now, the the e-mail sent by my local Pretix was received in my local e-mail server.
Yes, I started the plugin already.
Guten tag!
Ich arbeite jetzt mit Django. Pretix brauche es. Ich kenne es nicht. Ich muss es studieren :)
While these instructions don't assume that you know a lot about pretix, they do assume that you have prior knowledge about Django (e.g. its view layer, how its ORM works, etc.).
Ich möchte wissen wie viel muss ich lernen.
[This is a translation to my basic German] Hello!
I am now working with Django. Pretix uses it. I don't know it. I have to study it :)
I would like to know ho much I have to learn
In other words, it seems that I will have to learn Django to continue with this. If you have some recommendations on how much I have to learn, they are welcome. Thanks.
Hi Edgar,
Sorry, I know Django only superficially, and Pretix even less, so I can't give you hints as to what to focus on.
Good luck!
Christian
__init__.py
?Looking into Plugins,
I realised that Pretix has plugins' definition in the app's root directory, as compared to what the documentation of a regular app does (which has the class
declaration in the apps.py
). I think that this was the way in which they used to be declared in previous versions of Django.
Apikey Sandbox
?CLOSED: [2020-09-15 mar 15:55]
Hi, Christian. My local service for Taler seems to work
curl --no-progress-meter http://localhost:8888/ 2>&1
This is a GNU Taler merchant backend. See https://taler.net/.
But I don't know how to talk to it. I would like to do something like
import requests reqres = requests.get("https://localhost:8888", headers={"Authorization": "ApiKey sandbox"})
print(reqres)
Hi Edgar,
First of all, if you are running your own service directly, you don't need the ApiKey as that is checked by our nginx proxy:
location /private { # match the ApiKey part ignoring case, and the actual key # with case-sensitivity on. if ($http_authorization !~ "(?i)ApiKey (?-i)sandbox") { return 401; } proxy_set_header X-Forwarded-Host "backend.test.taler.net"; proxy_set_header X-Forwarded-Proto "http"; proxy_pass http://unix:/home/taler-test/sockets/merchant.http:/private; proxy_redirect off; proxy_set_header Host $host; }
(from nginx configuration).
import requests reqres = requests.get("https://localhost:8888", headers={"Authorization": "ApiKey sandbox"})
print(reqres)
This looks correct, except you don't even need the "Authorization" header unless there is some reverse proxy setup requiring authorization.
Try:
requests.get("https://localhost:8888/private/instances")
first. If that works (you don't get a failure), create your first instance:
curl -H "Content-Type: application/json" -X POST -d '{"payto_uris":["payto://x-taler-bank/localhost/pos"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"KUDOS:1","default_max_deposit_fee":"KUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_ms": 3600000},"default_pay_delay":{"d_ms": 3600000}}' http://localhost:8888/private/instances
Given that (do the requests.get again to check if you now got an instance setup!), you should now be able to create your first order (against the 'default' instance):
curl -H "Content-Type: application/json" -X POST -d '{"order":{"amount":"KUDOS:2","summary":"The summary","fulfillment_url":"https://example.com/"}}' http://localhost:8888/private/orders
Happy hacking!
Christian
Thank you very much, Christian.
This seems to be super basic, and I apologise beforehand. There is nginx in my system, but it is only there as a Pretix requirement. Trying both my and PostgreSQL's user (postgres
) did not work either. It seems to be an SSL error, but I don't know how to fix it.
$ sudo -u postgres python Python 3.8.5 (default, Jul 27 2020, 08:42:51) [GCC 10.1.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import requests >>> reqres = requests.get("https://localhost:8888/private/instances");
Traceback (most recent call last): File "/usr/lib/python3.8/site-packages/urllib3/connectionpool.py", line 670, in urlopen httplib_response = self._make_request( File "/usr/lib/python3.8/site-packages/urllib3/connectionpool.py", line 381, in make_request self._validate_conn(conn) File "/usr/lib/python3.8/site-packages/urllib3/connectionpool.py", line 978, in _validate_conn conn.connect() File "/usr/lib/python3.8/site-packages/urllib3/connection.py", line 362, in connect self.sock = ssl_wrap_socket( File "/usr/lib/python3.8/site-packages/urllib3/util/ssl.py", line 384, in ssl_wrap_socket return context.wrap_socket(sock, server_hostname=server_hostname) File "/usr/lib/python3.8/ssl.py", line 500, in wrap_socket return self.sslsocket_class._create( File "/usr/lib/python3.8/ssl.py", line 1040, in _create self.do_handshake() File "/usr/lib/python3.8/ssl.py", line 1309, in do_handshake self._sslobj.do_handshake() OSError: [Errno 0] Error
During handling of the above exception, another exception occurred:
Traceback (most recent call last): File "/usr/lib/python3.8/site-packages/requests/adapters.py", line 439, in send resp = conn.urlopen( File "/usr/lib/python3.8/site-packages/urllib3/connectionpool.py", line 726, in urlopen retries = retries.increment( File "/usr/lib/python3.8/site-packages/urllib3/util/retry.py", line 403, in increment raise six.reraise(type(error), error, stacktrace) File "/usr/lib/python3.8/site-packages/urllib3/packages/six.py", line 734, in reraise raise value.with_traceback(tb) File "/usr/lib/python3.8/site-packages/urllib3/connectionpool.py", line 670, in urlopen httplib_response = self._make_request( File "/usr/lib/python3.8/site-packages/urllib3/connectionpool.py", line 381, in _make_request self._validate_conn(conn) File "/usr/lib/python3.8/site-packages/urllib3/connectionpool.py", line 978, in _validate_conn conn.connect() File "/usr/lib/python3.8/site-packages/urllib3/connection.py", line 362, in connect self.sock = ssl_wrap_socket( File "/usr/lib/python3.8/site-packages/urllib3/util/ssl.py", line 384, in ssl_wrap_socket return context.wrap_socket(sock, server_hostname=server_hostname) File "/usr/lib/python3.8/ssl.py", line 500, in wrap_socket return self.sslsocket_class._create( File "/usr/lib/python3.8/ssl.py", line 1040, in _create self.do_handshake() File "/usr/lib/python3.8/ssl.py", line 1309, in do_handshake self._sslobj.do_handshake() urllib3.exceptions.ProtocolError: ('Connection aborted.', OSError(0, 'Error'))
During handling of the above exception, another exception occurred:
Traceback (most recent call last): File "", line 1, in File "/usr/lib/python3.8/site-packages/requests/api.py", line 76, in get return request('get', url, params=params, **kwargs) File "/usr/lib/python3.8/site-packages/requests/api.py", line 61, in request return session.request(method=method, url=url, **kwargs) File "/usr/lib/python3.8/site-packages/requests/sessions.py", line 530, in request resp = self.send(prep, **send_kwargs) File "/usr/lib/python3.8/site-packages/requests/sessions.py", line 643, in send r = adapter.send(request, **kwargs) File "/usr/lib/python3.8/site-packages/requests/adapters.py", line 498, in send raise ConnectionError(err, request=request) requests.exceptions.ConnectionError: ('Connection aborted.', OSError(0, 'Error'))
Yes, if you are using the backend directly, you should be using HTTP and not HTTPS and also you then don't need the authorization header.
Hi! I was able to check the instance. I knew that it was a silly mistake. Sorry. Thanks again :) .
Doc/src