taler-pretix-interface.org 178 KB

Description

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).

Tasks

DONE Install Mumble

CLOSED: [2020-07-29 mié 19:12]

To chat on the internet

Description

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.

Installation and configuration

client portion of the software (Mumble) and the server portion (Murmur).

pacman -S murmur mumble

DONE create repository

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

Description

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 ;-)).

DONE use sphinx documentation with Org

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

Description

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 bare-bones for Sphinx doc

# 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 response types

Try to do something with Taler (local service)

<> 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

401 Authorization Required


nginx

  • Architecture overview
  • frontend
  • back office
  • backend
  • DBMS
  • Postgres
  • Terminology
  • Tutorial (with examples)

DONE Install Pretix

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:

DONE Download documentation

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

DONE Set up Post fix

CLOSED: [2020-08-04 mar 22:32] :LOGBOOK: CLOCK: [2020-08-04 mar 20:56]--[2020-08-04 mar 22:25] => 1:29 :END:

Pretix requirement

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

  1. Skip the welcome screen (click in the button to use existing accounts);
  2. Click in the Settings button at top right (similar to Chrome settings) then click on Preferences > Account Settings
  3. Under Account Actions choose "Add Other Account"
  4. Select "Unix Mailspool (Movemail)"
  5. Your account will be *@localhost* (of course, replace with your user account). Don't use @(none), use @localhost Ingoing and Outgoing server will be: localhost Restart (close and reopen) Thunderbird. #+end_QUOTE

#+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

DONE Set up HTTPS resverse proxy (nginx)

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:

Pretix requirement

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

DONE Set up Redis

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

  • https://redis.io/

#+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

    #+begin_SRC diff --- artix/PKGBUILD 2020-08-07 19:06:54.399545805 -0500 +++ parabola/PKGBUILD 2020-08-07 19:08:15.039546038 -0500 @@ -11,14 +11,13 @@ arch=('any') url="https://github.com/artix-linux/packages-galaxy" license=('GPL2') -groups=('openrc-galaxy') depends=('openrc' 'redis') conflicts=('systemd-sysvcompat') backup=('etc/conf.d/redis') -source=("redis.confd::${_url}/dev-db/redis/files/redis.confd"
  • "redis.initd::${_url}/dev-db/redis/files/redis.initd-4")
  • -sha256sums=('8c68e29dc88c8ad99b9212a448d313d1406ef02c7638398b2e48ea1ca8aa937b'
  • '94257e625ee8c0212dd8afdaa9871796f9944a3ee68641cbde902a1a8a9c6fa9')
  • +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 @@ -31,6 +30,8 @@ -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(){ #+end_SRC

  • Restrict port to localhost

#+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

DONE Set up Pretix

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:

  • manual_smallscale.rst

#+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

  • What would be needed to create a package (skipped)

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: Extract requirements from Pretix into a suitable format for PKGBUILD #+begin_SRC python :results replace output :dir "/tmp/pretix-ca2dd0d6b6ab9225b96bcdb84d8398321e4e3fab/src/requirements/" # Temporarily open (and close file automatically) with open("production.txt") as fd: # Loop over all requirements for VAL in fd.readlines(): if VAL[0] != "#": VAL = VAL.split("#")[0] # Delete newlines and extra space VAL = VAL.rstrip() # Check if there is a wildcard at the end if VAL[-1] == "*": # Break string in 2 in the last "=" to get # version ver = VAL.rsplit("=", 1)[-1] # Remove the wildcard ver = ver.rstrip(".*") # Get the minimum subversion considered subver = int(ver.split(".")[-1]) # Get upper limit for subversion (replace the # last number) verlim = ver[:-len(str(subver))] \
  • str(subver + 1)
  • strout = "'{0:s}>={1:s}' '{0:s}<{2}'" strargs = [VAL.split("=", 1)[0], ver, verlim] print(strout.format(*strargs)) elif not "," in VAL and "==" in VAL: print("'{0:s}'".format(VAL.replace("==", "="))) else: print(VAL) #+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

  • Installing Pretix with virtualenv

#+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

  • Example (not used): OpenRC service for celery (Alpine Linux)

#+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

  • OpenRC services for pretix
  • #+name: blk-pretix-web.openrc #+CAPTION: OpenRC service for pretix web (=pretixhome="/var/pretix"=) #+begin_SRC sh :file /etc/init.d/pretix-web :tangle "media/pretix-web" #!/usr/bin/openrc-run

# /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

  • Cron job

#+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

  • ps -u pretix -o args= ::
  • -u pretix
    commands run by pretix
    -o args=
    show the full command (with arguments); = avoids headings to be printed
  • Start Pretix services

#+begin_SRC bash :dir "/sudo::" :cache no rc-service pretix-web start rc-service pretix-worker start #+end_SRC

  • Setup Pretix local domain (with nginx)

#+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

Final configuration files

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 <>

DONE Learn Django

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

Tutorial

: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

Poll app

: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:

Creating the app

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), ]

Sending keyword arguments to the poll (not in tutorial)

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'}
Database setup

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:

  • Create a database schema (CREATE TABLE statements) for this app.
  • Create a Python database-access API for accessing Question and Choice
  • objects.

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:

  • Change your models (in models.py).
  • Run python manage.py makemigrations to create migrations for those changes (this can be added to your version control system)
  • Run python manage.py migrate to apply those changes to the
  • database.
API

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")

  • Field lookups intro
Admin user

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)

Views

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 %}

    {% for question in latest_question_list %}
  • {{ question.question_text }}
  • {% endfor %}
{% else %}

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

<>

<>

<>

<>

{{ question.question_text }}

    {% for choice in question.choice_set.all %}
  • {{ choice.choice_text }}
  • {% endfor %}

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

<>

<>

<>

<>

  • {{ question.question_text }}
  • 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'), ...

    Namespaces

    from django.urls import path from . import views

    app_name = 'polls' <>

    {% if latest_question_list %}

      {% for question in latest_question_list %}
    • {{ question.question_text }}
    • {% endfor %}
    {% else %}

    No polls are available.

    {% endif %}

    Making something up

    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

    <>

    <>

    <>

    <>

    <>

      In the tutorial, the view only refers to =question=, but that is the name of a variable in [[def-django-tut-poll-detail-404.py][=def detail=]], but also the /dictionary/ argument of =render=
    • Test if the template gets the arguments passed by the definition of the view
    • if mytext ({{ mytext }})) can be keyword argument (of the dictionary of render in the definition of the test view)
    • #+NAME: blk-django-tut-test.html #+CAPTION: Create a custom view template for my =test= view #+begin_SRC html :tangle media/django/mytut/polls/templates/polls/test.html {% if mytext and mylist %}

      Using a loop to add newlines to a list

      This section shows a list split by <br>. The list is (request, question). The request is part of the url (for the template), and the question is the result of get_list_or_404(Question, question_text__startswith=testarg) (testarg is the text typed-into the url after mytest). Both the request and the question are prepended with This is the request or This is the list of questions\n, respectively

      {% for mysubtext in mylist %} {{ mysubtext }}
      {% endfor %}

    Note how the \n makes no difference here.

    Using linebreaksbr from the builtins

    In this section, a string with newlines (\n) is split by a Django builtin (linebreaksbr). The string is formed in Python as "{0}\n{1}".format(request, question), where request and question have the same meaning as above

    {{ mytext|linebreaksbr }}

    Note how the \n does makes a difference here, as compared to the previous case {% else %}

    No text :).

    {% endif %} #+end_SRC

    • Changing the structure of the path to see if it works (note that test.html has namespace polls:testing)
    • Also, the type of variable
    • Set a name (name='testing')
    • #+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 :noweb yes from django.urls import path from . import views

    app_name = 'polls' <> urlpatterns += [path('mytest/', views.test, name='testing')] #+END_SRC

      In conclusion, the logic is:
    1. URLConf (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)
    2. The corresponding function in views.py is executed, and the returned value is passed to the HTML template (test.html)
    3. The HTML template (test.html) replaces Django's built-in code, which can include Python-like primitives
    Form

    {{ question.question_text }}

    {% 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

    <>

    <>

    <>

    <>

    <>

    {{ question.question_text }}

      {% for choice in question.choice_set.all %}
    • {{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}
    • {% endfor %}

    Vote again?

    Generic views

    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>>
    
    
    Automated testing

    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

    Django test client

    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    \n\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)

    Static files
    • Customize your app’s look and feel

    mkdir polls/static

    mkdir -p polls/static/polls

    li a { color: green; }

    body { background: white url("icon-cashier.svg") no-repeat; }

    {% load static %}

    <>

    Customize admin site

    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)

    Signals

    :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

    Setting up signals (a signal for every request)

    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

    • From Applications reference (online)
    • #+begin_quote If you’re creating a pluggable app called “Rock ’n’ roll”, here’s how you would provide a proper name for the admin: #+end_quote #+CAPTION: rock_n_roll/apps.py #+begin_src python from django.apps import AppConfig

    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)

    Use only some signals

    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)

    Define signals

    DONE Take a look at Pretix

      CLOSED: [2020-09-06 dom 18:17]
    • State "NEXT" from "HOLD" [2020-09-01 mar 14:15]
    • State "HOLD" from "NEXT" [2020-08-20 jue 20:22] \\
    • I will learn Django first :LOGBOOK: CLOCK: [2020-09-06 dom 17:25]--[2020-09-06 dom 17:54] => 0:29 CLOCK: [2020-09-06 dom 17:10]--[2020-09-06 dom 17:23] => 0:13 CLOCK: [2020-09-06 dom 16:38]--[2020-09-06 dom 16:45] => 0:07 CLOCK: [2020-09-06 dom 15:58]--[2020-09-06 dom 16:29] => 0:31 CLOCK: [2020-09-03 jue 15:10]--[2020-09-03 jue 15:15] => 0:05 CLOCK: [2020-09-03 jue 14:14]--[2020-09-03 jue 14:17] => 0:03 CLOCK: [2020-09-03 jue 11:36]--[2020-09-03 jue 11:37] => 0:01 CLOCK: [2020-09-01 mar 18:40]--[2020-09-01 mar 18:56] => 0:16 CLOCK: [2020-09-01 mar 18:21]--[2020-09-01 mar 18:32] => 0:11 CLOCK: [2020-09-01 mar 17:50]--[2020-09-01 mar 18:21] => 0:31 CLOCK: [2020-09-01 mar 14:15]--[2020-09-01 mar 15:00] => 0:45 CLOCK: [2020-08-19 mié 20:29]--[2020-08-19 mié 20:43] => 0:14 CLOCK: [2020-08-19 mié 09:33]--[2020-08-19 mié 09:47] => 0:14 CLOCK: [2020-08-15 sáb 20:08]--[2020-08-15 sáb 20:33] => 0:25 :END:

    Configure pretix

    Once Pretix is installed, it needs to be configured (teams, and such).

    • A database seems to be ready to use, with an initial admin user
    • Point your browser to http://localhost:8345
    • The default password for the admin user (in this case admin@localhost is admin)
    • Disable the automatic update checks (if you want)
    • Create an organizer account
    • #+begin_QUOTE The basis of all your operations within pretix is your organizer account. It represents an entity that is running events ... events within the same organizer account are assumed to belong together... -- [[file:pretix-doc/doc/user/organizers/account.rst][account.rst]] #+end_QUOTE
    1. go into http://localhost:8345/control/login

    #+caption: Landing page for Pretix media/pretix-landing.png

    1. log-in as 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

    1. go into admin session

    #+caption: Go into administratative session with the admin user media/pretix-admin-ini-dashboard--admin-mode-mark.svg

    #+caption: Initial screen as admin in administrative mode file:media/pretix-admin-admin_mode.png

    1. Go into organizers mode and follow your instincts (or check the documentation)

    #+caption: Going into organizers as /admin/ file:media/pretix-admin-admin_mode--orgnanizers-mark.svg

    Do something with Pretix

    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.

    Plugins (creating one on Pretix server)

    Before I can create a payment plugin, I need to

    Please read `Creating a plugin ` first, if you haven't already.

    • Pretix' plugins doc (local)

    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

      diff --git a/cookiecutter.json b/cookiecutter.json index 6b60cdb..6d7a18e 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -7,7 +7,7 @@ "author_email": "Your email", "year": "Current year", "short_description": "Short description",
    • "license": ["Apache", "pretix Enterprise"],
    • "license": ["Apache", "pretix Enterprise", "GPLv3"],
    • "min_basever": "2.7.0", "category": ["FEATURE", "PAYMENT", "INTEGRATION", "CUSTOMIZATION", "FORMAT", "API"] } diff --git a/{{cookiecutter.repo_name}}/LICENSE b/{{cookiecutter.repo_name}}/LICENSE index 4ad4041..49c5311 100644 --- a/{{cookiecutter.repo_name}}/LICENSE +++ b/{{cookiecutter.repo_name}}/LICENSE @@ -21,4 +21,20 @@ rami.io Software development You can obtain a paid license and the full license text at sales@pretix.eu.

    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 documentation (local)

    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

    Payment plugin (from Paypal™)

    I basically copied Pretix' Paypalplugin and replaced Paypal ™ with Taler

    <>

    TODO Test if setting the Pretix plugin definition as a Django application (apps.py) works

    NEXT Adapt PayPal™ plugin to Taler

    :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 into ours, and updated the database. It is now time to see how to really connect Taler with Pretix.

    • taler-merchant-api-tutorial.rst

    Restart or start service

    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 taler-merchant-api-tutorial.rst)

    (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| +-------------- -------------- --------------+ | ^ | | +--------------------------------------------------

    media/plugin-basic-flowchart.png

    python setup.py develop

    • [X] Replace _ with gettext_lazy
    • I don't even remember where, but =_= (deprecated) needs to be replaced by =gettext_lazy=

    Replace PayPal™ with Taler in the code

    • pretix.plugins.paypalpretix.plugins.taler
    • [X] __init__.py__init__.py
    • [X] payment.pypayment.py
    • [X] views.pyviews.py
    • Replace any paypal with Taler
    • [X] __init__.py__init__.py
    • [ ] payment.pypayment.py
    • [ ] views.pyviews.py
    • Delete paypalrestsdk
    • [ ] __init__.py__init__.py
    • [ ] payment.pypayment.py
    • [ ] views.pyviews.py

    Create an HTTP request

    From the conversation with Christian Grothoff and the documentation, 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 taprelix/pretix_taprelix/payment.py

    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.")
      #     )
    

    Auxiliary noexport

    upd8 repo

    :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:

    Questions noexport

    :LOGBOOK: CLOCK: [2020-08-15 sáb 20:33]--[2020-08-15 sáb 20:47] => 0:14 :END:

    DONE Refunds and wiring doc may need rewording [2020-07-30 jue]

    CLOSED: [2020-08-02 dom 17:12]

    After reading this 2 or 3 times in docs/taler-merchant-manual.org:

    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.

    [2020-08-02 dom]

    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.

    DONE Is there a need to have taxable tips? [2020-07-30 jue]

    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?

    [2020-08-02 dom]

    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.

    DONE Sources need to stop referencing 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
    

    Explanation of used commands (for completeness)

    • find ... ::
    • =.=
      current directory (taler-exchange root)
      -type f
      files only
      \( -name '*.c' -o -name '*.h' \)
      files ending in =.c= or =.h=
      -exec ... \{\} +
      run a command on the found files one by one
    • grep ... ::
    • -l
      print names only
      -e
      pattern_list are separated by a
      .
      'include
      find include <taler/
      while ...
      read full names with spaces and special characters (in a loop)
      IFS=
      clear the separator of input
    • read ... ::
    • -r
      prevents read from treating backslash as a special character
      -d$'\n'
      make sure that
      printf
      print to std output (screen)
      sed ...
      remove taler/
      -i
      edit file in-place
      s% ... % ... %g
      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)
      \(...\)
      stores a string in a registry, which can then be accessed by \1, \2, ..., \9

    [2020-08-02 dom]

    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!

    TODO [2020-08-02 dom]

    > ---------------------------------------- > 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 :) !

    DONE Is the server blocking me? [2020-08-01 sáb]

    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?

    [2020-08-01 sáb]

    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!

    [2020-08-01 sáb]

    Duh! Thanks

    TODO Shall we move the warning about global access to the top? [2020-08-01 sáb]

    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.

    DONE What is a RESTful API?

    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

    • https://restfulapi.net/
    • #+begin_QUOTE REST is acronym for REpresentational State Transfer. It is architectural style for distributed hypermedia systems and was first presented by Roy Fielding in 2000 in his famous dissertation. #+end_QUOTE
      #+begin_QUOTE
    1. Client–server

    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.

    1. Stateless

    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.

    1. Cacheable

    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.

    1. Uniform interface
      By applying the software engineering principle of generality to the component interface, the overall system architecture is simplified and the visibility of interactions is improved. In order to obtain a uniform interface, multiple architectural constraints are needed to guide the behavior of components. REST is defined by four interface constraints[fn::I changed the format a bit]:
    • identification of resources;
    • manipulation of resources through representations;
    • self-descriptive messages, and
    • hypermedia as the engine of application state.
    1. Layered system

    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.

    1. Code on demand (optional)

    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.

    What do I need to set a local backend? [2020-08-01 sáb]

    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!

    [2020-08-01 sáb]

    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

    [2020-08-02 dom]

    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.

    [2020-08-02 dom]

    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...

    [2020-08-02 dom]

    > 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

    DONE Should we change the title of a section or show how to accept payments? [2020-08-02 dom]

    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).

    [2020-08-03 lun]

    I've changed it to 'Merchant Payment Processing'. May be better, not sure.

    DONE Consider Tryton [2020-08-02 dom]

    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).

    [2020-08-03 lun]

    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 ;-).

    [2020-08-08 sáb]

    > 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.

    DONE Is my mail service for Pretix working? [2020-08-15 sáb]

    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 attaching what I got in my e-mail client (see the Emacs config).

    Thanks!

    [2020-08-16 dom]

    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

    [2020-08-16 dom]

    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

    [2020-08-18 mar] (not sent)

    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 -

    [2020-08-19 mié]

    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!

    [2020-09-01 mar]

    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.

    How far deep into Django? [2020-08-19 mié]

    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.

    [2020-08-20 jue]

    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

    Why is a Pretix plugin declared in the __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.

    DONE How do I set a 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)

    [2020-09-14 lun]

    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

    [2020-09-14 lun]

    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'))

    [2020-09-15 mar]

    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.

    [2020-09-15 mar]

    Hi! I was able to check the instance. I knew that it was a silly mistake. Sorry. Thanks again :) .

    Info noexport

    • Mails: Job from FSF

    Configuration noexport

    Exporting process

    1. Export to Doc/src
    2. [[*Change exporting directory][Change exporting directory]]
    1. Export this file as rst
    1. Run this
    2. #+begin_src bash :dir "./Doc" make -k html #+end_src