Compare commits

...

30 Commits

Author SHA1 Message Date
Stan Drozd d43223b591
labelmaker: Make label size configurable at runtime 2021-03-13 05:25:53 +01:00
Stan Drozd e3e0bb068a
Exile labelmaker API calls to a separate module 2021-03-13 03:37:51 +01:00
Stan Drozd b40485bab5
labelmaker: Automatically choose correct label orienation 2021-03-13 03:37:28 +01:00
Stan Drozd 5af74c2287
Make Label.print() retarded 2021-03-12 02:43:45 +01:00
Stan Drozd 11836f756e
spejstore: Dedup and adapt label printing in models, add SHORT_HOST
SHORT_HOST is a new option that lets you specify which of the allowed
hosts to use for inventory QR codes
2021-03-12 02:41:48 +01:00
Stan Drozd a2eb19209d
main.rb: Stop using a base URL for links 2021-03-11 22:22:20 +01:00
Stan Drozd 9c80c68716
labelmaker/default.nix: prune redundant deps 2021-03-11 22:15:55 +01:00
Stan Drozd de1cc4d303
labelmaker: Make the printer name configurable via an env 2021-03-10 01:42:08 +01:00
Stan Drozd 81552ceff2
labelmaker: Stop calling back into spejstore, rename API methods 2021-03-09 02:34:12 +01:00
Stan Drozd 11615a2bc2
Tweak the Dockerfile, fix label errors, WIP: cups on labelmaker 2021-03-07 21:25:40 +01:00
Stan Drozd cb6eec3c9f
Improve nixified Dockerfile, adjust docker-compose yamls 2021-03-07 03:30:08 +01:00
Stan Drozd 9a7c2271e7
main.rb: rename base URLs 2021-03-07 03:29:40 +01:00
Stan Drozd 546a4f026b
Dockerfile.nixified: Also use the manage-py attrib in CMD 2021-03-07 00:37:49 +01:00
Stan Drozd 94f67f43de
Nixify labelmaker, make //default.nix an attrset, adjust Dockerfile 2021-03-07 00:22:50 +01:00
Stan Drozd a5ad3423ce
Make BACKEND_URL and CODE_PREFIX configurable via envs 2021-03-06 23:41:27 +01:00
Stan Drozd 63bc86ff96 Add 'labelmaker/' from commit 'b49426a9df1bed090eff8e058addbe6a8bd972ec'
git-subtree-dir: labelmaker
git-subtree-mainline: 35b29fba9b
git-subtree-split: b49426a9df
2021-03-06 20:18:16 +01:00
Stan Drozd 35b29fba9b
Finalize nix build, add a nixified container 2021-03-03 00:53:14 +01:00
Stan Drozd 5d2f99287e
Bump Python in Dockerfile, make it work with poetry, update lockfile 2021-02-28 01:39:05 +01:00
Stan Drozd fae8d2b219
Add requirements.txt contents to pyproject.toml, add lockfile 2021-02-28 00:45:14 +01:00
Stan Drozd 140eeb43da
Update dependencies (cherry-picked from ar)
As far as i can see, the only breaking change is the change from
@detail_route to @action(detail=True) decorator in
rest_framework.decorators.
(famous last words)
2021-02-28 00:44:56 +01:00
Stan Drozd 5aa9f56d3e
Poetry init, move shell deps to nativeBuildInputs, 2021-02-28 00:26:11 +01:00
Stan Drozd 47eda90387
Initialize niv, lorri, add nixpkgs.python3 as common dep 2021-02-27 23:15:21 +01:00
radex b49426a9df Prepare for zebra, add owner name if present 2020-05-28 22:00:02 +02:00
radex c43b83282a Fix render after update, support new spejstore short_id lookup 2020-05-28 22:00:02 +02:00
radex 56321627a6 Add missing fonts 2020-05-28 22:00:02 +02:00
radex 3c9c0f9ba3 Bump dependencies, add basic readme 2020-05-28 22:00:02 +02:00
informatic 4cab9f4707 Fix text wrapping 2017-09-25 13:37:13 +02:00
informatic 3a98c23838 Better margins, sizing, layout... 2017-05-29 00:51:24 +02:00
informatic f3e0f3ff9e Fix backend URLs... 2017-05-28 22:25:38 +02:00
informatic e6c4ebee5d Initial commit 2017-05-28 02:53:46 +02:00
35 changed files with 1595 additions and 51 deletions

1
.envrc Normal file
View File

@ -0,0 +1 @@
eval "$(lorri direnv)"

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ postgres-hstore/
.ropeproject/
docker-compose.override.yml
build_static
*.egg-info

View File

@ -1,11 +1,13 @@
FROM python:3.5.9@sha256:3a71fd2dac2343263993f4ab898c9398dfbfd0235dafe41e784876b69bdfa899
FROM python:3.8
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
ADD requirements.txt /code/
RUN pip install --no-cache-dir -r requirements.txt
ADD pyproject.toml /code
ADD poetry.lock /code
RUN pip install poetry --no-cache-dir
RUN poetry install
RUN wget https://github.com/vishnubob/wait-for-it/raw/8ed92e8cab83cfed76ff012ed4a36cef74b28096/wait-for-it.sh -O /usr/local/bin/wait-for-it && chmod +x /usr/local/bin/wait-for-it
ADD . /code/
RUN python manage.py collectstatic
ADD . /code
RUN poetry run ./manage.py collectstatic
CMD bash -c "python manage.py migrate && python manage.py runserver 0.0.0.0:8000"
CMD bash -c "poetry run python3 ./manage.py migrate && poetry run python3 ./manage.py runserver 0.0.0.0:8000"

49
Dockerfile.nixified Normal file
View File

@ -0,0 +1,49 @@
# syntax = docker/dockerfile:1.2
FROM nixos/nix as nix-base
ENV PYTHONUNBUFFERED 1
RUN nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs && \
nix-channel --update
WORKDIR /code
RUN wget https://github.com/vishnubob/wait-for-it/raw/8ed92e8cab83cfed76ff012ed4a36cef74b28096/wait-for-it.sh -O /usr/local/bin/wait-for-it.sh && chmod +x /usr/local/bin/wait-for-it.sh
RUN nix-env -iA nixpkgs.git nixpkgs.bash nixpkgs.coreutils
ADD ./nix ./nix
ADD ./spejstore.nix .
ADD ./labelmaker/*.nix ./labelmaker/
ADD ./labelmaker/Gemfile* ./labelmaker/
ADD ./poetry.lock .
ADD ./pyproject.toml .
FROM nix-base as spejstore
RUN touch spejstore.py # poetry must think there's a module, so we mock a trivial one
RUN nix-env -if spejstore.nix
RUN rm spejstore.py
ADD *.env .
ADD *.py .
ADD auth ./auth
ADD dist ./dist
ADD python-modules ./python-modules
ADD spejstore ./spejstore
ADD static ./static
ADD storage ./storage
ADD templates ./templates
RUN nix-env -if ./spejstore.nix
CMD /usr/local/bin/wait-for-it.sh db:5432 && manage-py migrate && \
manage-py runserver 0.0.0.0:8000
FROM nix-base as labelmaker
RUN nix-env -if ./labelmaker/default.nix
ADD labelmaker labelmaker
RUN nix-env -if ./labelmaker/default.nix
ENV LPR_PRINTER_NAME=fake-printer
CMD labelmaker

6
common-deps.nix Normal file
View File

@ -0,0 +1,6 @@
{ sources ? import ./nix/sources.nix, pkgs ? import sources.nixppkgs {} }:
with pkgs;
[
git
postgresql
]

10
default.nix Normal file
View File

@ -0,0 +1,10 @@
{ sources ? import ./nix/sources.nix
, poetry2nix-olay ? import
# ../poetry2nix/overlay.nix
"${sources.poetry2nix}/overlay.nix"
, nixpkgs ? sources.nixpkgs
}:
{
manage-py = import ./spejstore.nix { inherit sources; };
labelmaker = import labelmaker/default.nix { inherit sources; };
}

View File

@ -1,6 +1,11 @@
version: "3"
version: "3.4"
services:
web:
environment:
- SPEJSTORE_ENV=dev
- SPEJSTORE_ALLOWED_HOSTS=localhost,127.0.0.1
- SPEJSTORE_ALLOWED_HOSTS=web,localhost,127.0.0.1
- SPEJSTORE_LABEL_API=http://labelmaker:4567
- SPEJSTORE_SHORT_HOST=web
labelmaker:
environment:
LPR_CMD_PATH: "true"

View File

@ -1,4 +1,4 @@
version: "3"
version: "3.4"
services:
db:

View File

@ -1,15 +1,16 @@
version: "3"
version: "3.4"
services:
db:
build: postgres-hstore
restart: always
environment:
- POSTGRES_HOST_AUTH_METHOD=trust
web:
build: .
build:
context: .
dockerfile: Dockerfile.nixified
target: spejstore
restart: always
command: bash -c "python manage.py migrate && python manage.py runserver 0.0.0.0:8000"
volumes:
- .:/code
ports:
@ -20,3 +21,14 @@ services:
- SPEJSTORE_CLIENT_ID
- SPEJSTORE_SECRET
- SPEJSTORE_ENV
labelmaker:
build:
context: .
dockerfile: Dockerfile.nixified
target: labelmaker
depends_on:
- web
ports:
- "4567:4567"

12
labelmaker/.editorconfig Normal file
View File

@ -0,0 +1,12 @@
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
[*.rb]
indent_style = spaces
indent_size = 2

2
labelmaker/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.bundle
vendor

10
labelmaker/Gemfile Normal file
View File

@ -0,0 +1,10 @@
source 'https://rubygems.org'
gem 'prawn'
gem 'prawn-qrcode', :git => 'https://github.com/jabbrwcky/prawn-qrcode'
gem 'prawn-svg'
gem 'sinatra'
gem 'color'
gem 'excon'
gem 'rmagick'
gem 'json'

61
labelmaker/Gemfile.lock Normal file
View File

@ -0,0 +1,61 @@
GIT
remote: https://github.com/jabbrwcky/prawn-qrcode
revision: 08e457af2d6661ede3346a3a3dc3b99a9df9fd9b
specs:
prawn-qrcode (0.5.1)
prawn (>= 1)
rqrcode (>= 1.0.0)
GEM
remote: https://rubygems.org/
specs:
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
chunky_png (1.3.11)
color (1.8)
css_parser (1.7.1)
addressable
excon (0.73.0)
json (2.3.0)
mustermann (1.1.1)
ruby2_keywords (~> 0.0.1)
pdf-core (0.7.0)
prawn (2.2.2)
pdf-core (~> 0.7.0)
ttfunk (~> 1.5)
prawn-svg (0.30.0)
css_parser (~> 1.6)
prawn (>= 0.11.1, < 3)
public_suffix (4.0.5)
rack (2.2.2)
rack-protection (2.0.8.1)
rack
rmagick (4.1.2)
rqrcode (1.1.2)
chunky_png (~> 1.0)
rqrcode_core (~> 0.1)
rqrcode_core (0.1.2)
ruby2_keywords (0.0.2)
sinatra (2.0.8.1)
mustermann (~> 1.0)
rack (~> 2.0)
rack-protection (= 2.0.8.1)
tilt (~> 2.0)
tilt (2.0.10)
ttfunk (1.6.2.1)
PLATFORMS
ruby
DEPENDENCIES
color
excon
json
prawn
prawn-qrcode!
prawn-svg
rmagick
sinatra
BUNDLED WITH
2.1.4

14
labelmaker/README.md Normal file
View File

@ -0,0 +1,14 @@
# spejstore-labelmaker
```sh
bundle install
bundle exec ruby main.rb
```
try it out:
GET http://localhost:4567/api/1/preview/:label.png
GET http://localhost:4567/api/1/preview/:label.pdf
POST http://localhost:4567/api/1/print/:label
where :label is a `spejstore` label.id or item.short_id

View File

@ -0,0 +1,7 @@
{ sources ? import ./nix/sources.nix, pkgs ? import sources.nixpkgs {} }:
with pkgs;
[
cups
pkg-config
imagemagickBig
]

32
labelmaker/default.nix Normal file
View File

@ -0,0 +1,32 @@
{ sources ? import ../nix/sources.nix
, nixpkgs ? sources.nixpkgs
, common-deps ? import ./common-deps.nix { inherit sources; }
}:
let
pkgs = import nixpkgs {};
ruby = pkgs.ruby;
env = pkgs.bundlerEnv {
name = "labelmaker-env";
gemdir = ./.;
gemConfig.rmagick = attrs: {
buildInputs = common-deps;
};
buildInputs = common-deps;
inherit ruby;
};
src-drv = pkgs.stdenv.mkDerivation {
name = "labelmaker-src";
src = ./.;
nativeBuildInputs = common-deps ++ [ env ];
installPhase = ''
mkdir -p $out/share
cp -r . $out/share
'';
};
script = pkgs.writeScriptBin "labelmaker" ''
#!/bin/sh -e
${env}/bin/bundle exec ${ruby}/bin/ruby ${src-drv}/share/main.rb
'';
in
script

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
labelmaker/fonts/DejaVuSans.ttf Executable file

Binary file not shown.

223
labelmaker/gemset.nix Normal file
View File

@ -0,0 +1,223 @@
{
addressable = {
dependencies = ["public_suffix"];
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "1fvchp2rhp2rmigx7qglf69xvjqvzq7x0g49naliw29r2bz656sy";
type = "gem";
};
version = "2.7.0";
};
chunky_png = {
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "124najs9prqzrzk49h53kap992rmqxj0wni61z2hhsn7mwmgdp9d";
type = "gem";
};
version = "1.3.11";
};
color = {
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "10kgsdy86p72q6cf2k92larmbjc0crvd5xq7hy919zm8yvn1518a";
type = "gem";
};
version = "1.8";
};
css_parser = {
dependencies = ["addressable"];
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "04c4dl8cm5rjr50k9qa6yl9r05fk9zcb1zxh0y0cdahxlsgcydfw";
type = "gem";
};
version = "1.7.1";
};
excon = {
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "1zvphy60fwycl6z2h7dpsy9lgyfrh27fj16987p7bl1n4xlqkvmw";
type = "gem";
};
version = "0.73.0";
};
json = {
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "0nrmw2r4nfxlfgprfgki3hjifgrcrs3l5zvm3ca3gb4743yr25mn";
type = "gem";
};
version = "2.3.0";
};
mustermann = {
dependencies = ["ruby2_keywords"];
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "0ccm54qgshr1lq3pr1dfh7gphkilc19dp63rw6fcx7460pjwy88a";
type = "gem";
};
version = "1.1.1";
};
pdf-core = {
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "19llwch2wfg51glb0kff0drfp3n6nb9vim4zlvzckxysksvxpby1";
type = "gem";
};
version = "0.7.0";
};
prawn = {
dependencies = ["pdf-core" "ttfunk"];
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "1qdjf1v6sfl44g3rqxlg8k4jrzkwaxgvh2l4xws97a8f3xv4na4m";
type = "gem";
};
version = "2.2.2";
};
prawn-qrcode = {
dependencies = ["prawn" "rqrcode"];
groups = ["default"];
platforms = [];
source = {
fetchSubmodules = false;
rev = "08e457af2d6661ede3346a3a3dc3b99a9df9fd9b";
sha256 = "134jf2273zn4m3dfjg5m00pxspz4adsfia0062vkhcw5rnvqa4xi";
type = "git";
url = "https://github.com/jabbrwcky/prawn-qrcode";
};
version = "0.5.1";
};
prawn-svg = {
dependencies = ["css_parser" "prawn"];
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "0df3l49cy3xpwi0b73hmi2ykbjg9kjwrvhk0k3z7qhh5ghmmrn77";
type = "gem";
};
version = "0.30.0";
};
public_suffix = {
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "0vywld400fzi17cszwrchrzcqys4qm6sshbv73wy5mwcixmrgg7g";
type = "gem";
};
version = "4.0.5";
};
rack = {
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "10mp9s48ssnw004aksq90gvhdvwczh8j6q82q2kqiqq92jd1zxbp";
type = "gem";
};
version = "2.2.2";
};
rack-protection = {
dependencies = ["rack"];
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "1zyj97bfr1shfgwk4ddmdbw0mdkm4qdyh9s1hl0k7accf3kxx1yi";
type = "gem";
};
version = "2.0.8.1";
};
rmagick = {
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "0ajn6aisf9hh3x5zrs7n02pg5xy3m8x38gh9cn7b3klzgp3djla5";
type = "gem";
};
version = "4.1.2";
};
rqrcode = {
dependencies = ["chunky_png" "rqrcode_core"];
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "06lw8b6wfshxd61xw98xyp1a0zsz6av4nls2c9fwb7q59wb05sci";
type = "gem";
};
version = "1.1.2";
};
rqrcode_core = {
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "071jqmhk3hf0grsvi0jx5sl449pf82p40ls5b3likbq4q516zc0j";
type = "gem";
};
version = "0.1.2";
};
ruby2_keywords = {
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "17pcc0wgvh3ikrkr7bm3nx0qhyiqwidd13ij0fa50k7gsbnr2p0l";
type = "gem";
};
version = "0.0.2";
};
sinatra = {
dependencies = ["mustermann" "rack" "rack-protection" "tilt"];
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "0riy3hwjab1mr73jcqx3brmbmwspnw3d193j06a5f0fy1w35z15q";
type = "gem";
};
version = "2.0.8.1";
};
tilt = {
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "0rn8z8hda4h41a64l0zhkiwz2vxw9b1nb70gl37h1dg2k874yrlv";
type = "gem";
};
version = "2.0.10";
};
ttfunk = {
groups = ["default"];
platforms = [];
source = {
remotes = ["https://rubygems.org"];
sha256 = "0w0bjn6k38xv46mr02p3038gwk5jj5hl398bv5kr625msxkdhqzn";
type = "gem";
};
version = "1.6.2.1";
};
}

156
labelmaker/main.rb Normal file
View File

@ -0,0 +1,156 @@
# coding: utf-8
require 'rubygems'
require 'sinatra'
require 'rqrcode'
require 'prawn'
require 'prawn/measurements'
require 'prawn/qrcode'
require 'prawn-svg'
require 'color'
require 'excon'
require 'rmagick'
require 'json'
require 'zlib'
include Prawn::Measurements
# module Prawn
# module Text
# module Formatted #:nodoc:
# # @private
# class LineWrap #:nodoc:
# def whitespace()
# # Wrap by these special characters as well
# "&:/\\" +
# "\s\t#{zero_width_space()}"
# end
# end
# end
# end
# end
module Excon
class Response
def json!()
# Convenience function
JSON.parse(body)
end
end
end
# name -> height, width in millimeters
LABEL_SIZES = {
"11354" => [32 , 57],
"99012" => [89 , 36],
"zebra_large" => [100 , 60],
}
LABEL_SIZE= ENV['LABEL_SIZE'] || "11354"
if !LABEL_SIZES[LABEL_SIZE]
abort("Bruh moment - not printing, undefined LABEL_SIZE=#{LABEL_SIZE} fails to match any of\n#{LABEL_SIZES}")
end
LPR_CMD_PATH = ENV['LPR_CMD_PATH'] || 'lpr'
LPR_PRINTER_NAME = ENV['LPR_PRINTER_NAME'] || 'DYMO_LabelWriter_450'
def render_identicode(data, id, extent)
pts = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]
4.times do |n|
color = Color::HSL.from_fraction((id % 6) / 6.0, 1.0, 0.3).html[1..6]
id /= 6
save_graphics_state do
soft_mask do
fill_color 'ffffff'
polygon = [pts[n], [0.5, 0.5], pts[n+1]].map{ |v| [v[0]*bounds.height, v[1]*bounds.height] }
fill_polygon(*(polygon))
end
print_qr_code data, stroke: false,
extent: extent, foreground_color: color,
pos: [bounds.left, bounds.top]
end
end
fill_color '000000'
end
DYMO_11354_LABEL_SIZE = [32, 57]
DYMO_LABEL_SIZE = [89, 36]
ZEBRA_LABEL_SIZE = [100, 60]
def render_label(name, qr_text, metadata_text, size: LABEL_SIZES[LABEL_SIZE])
portrait = size[0] > size[1] # Decide if which orientation prints horizontally on the label
pdf = Prawn::Document.new(:page_layout => portrait ? :portrait : :landscape, page_size: size.map { |x| mm2pt(x) },
margin: [2, 2, 2, 6].map { |x| mm2pt(x) }) do
base_dir = File.dirname(__FILE__)
font_families.update("DejaVuSans" => {
normal: base_dir + "/fonts/DejaVuSans.ttf",
italic: base_dir + "/fonts/DejaVuSans-Oblique.ttf",
bold: base_dir + "/fonts/DejaVuSans-Bold.ttf",
bold_italic: base_dir + "/fonts/DejaVuSans-BoldOblique.ttf"
})
font 'DejaVuSans'
# Width of right side
qr_size = [bounds.height / 2, 27].max
# Right side
bounding_box([bounds.right - qr_size, bounds.top], width: qr_size) do
print_qr_code qr_text, stroke: false,
foreground_color: '000000',
extent: bounds.width, margin: 0, pos: bounds.top_left
text_box metadata_text,
at: [bounds.right - qr_size, -7], size: 8, align: :right, overflow: :shrink_to_fit
end
# Left side
bounding_box(bounds.top_left, width: bounds.width - qr_size) do
text_box name,
size: 40, align: :center, valign: :center, width: bounds.width-10,
inline_format: true, overflow: :shrink_to_fit, disable_wrap_by_char: true
end
end
pdf.render
end
set :bind, '0.0.0.0'
before do
request.body.rewind
@json_payload = JSON.parse request.body.read
# Validate and make available from all handlers
@name = @json_payload["name"] || halt(400, "Request JSON needs a 'name' field")
@qr_text = @json_payload["qr_text"] || halt(400, "Request JSON needs a 'qr_text' field")
@meta_text = @json_payload["meta_text"] || ""
end
post '/api/1/render/pdf' do
headers["Content-Type"] = "application/pdf; charset=utf8"
render_label(@name, @qr_text, @meta_text)
end
post '/api/1/render/png' do
headers["Content-Type"] = "image/png"
img = Magick::ImageList.new()
img = img.from_blob(render_label(@name, @qr_text, @meta_text)){ self.density = 200 }.first
img.format = 'png'
img.background_color = 'white'
img.to_blob
end
post '/api/1/print' do
temp = Tempfile.new('labelmaker')
temp.write(render_label(@name, @qr_text, @meta_text))
temp.close
cmd = "#{LPR_CMD_PATH} -P #{LPR_PRINTER_NAME} '#{temp.path}'"
system(cmd) || puts("Bruh moment (not printing) - running `#{cmd}` failed")
end

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
import os
import sys

18
nix/poetry2nix-olay.nix Normal file
View File

@ -0,0 +1,18 @@
self: super: {
# p2self & p2super refers to poetry2nix
poetry2nix = super.poetry2nix.overrideScope' (
p2nixself: p2nixsuper: {
# pyself & pysuper refers to python packages
defaultPoetryOverrides = p2nixsuper.defaultPoetryOverrides.extend (
pyself: pysuper: {
my-custom-pkg = super.my-custom-pkg.overridePythonAttrs (oldAttrs: {});
}
);
}
);
}

38
nix/sources.json Normal file
View File

@ -0,0 +1,38 @@
{
"niv": {
"branch": "master",
"description": "Easy dependency management for Nix projects",
"homepage": "https://github.com/nmattia/niv",
"owner": "nmattia",
"repo": "niv",
"rev": "af958e8057f345ee1aca714c1247ef3ba1c15f5e",
"sha256": "1qjavxabbrsh73yck5dcq8jggvh3r2jkbr6b5nlz5d9yrqm9255n",
"type": "tarball",
"url": "https://github.com/nmattia/niv/archive/af958e8057f345ee1aca714c1247ef3ba1c15f5e.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
},
"nixpkgs": {
"branch": "nixpkgs-unstable",
"description": "Nix Packages collection",
"homepage": "",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "870dbb751f4d851a3dfb554835a0c2f528386982",
"sha256": "0ydgjvwspfspm0fg7jhc5yxpm47lh4scqhwswranm82hk668cxzl",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/870dbb751f4d851a3dfb554835a0c2f528386982.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
},
"poetry2nix": {
"branch": "master",
"description": "Convert poetry projects to nix automagically [maintainer=@adisbladis] ",
"homepage": "",
"owner": "nix-community",
"repo": "poetry2nix",
"rev": "c6f356183361493bc556138eca452878b651b90c",
"sha256": "18zqhwbl0r064xhga9hcwxji7j7xivksdlkl2i8avvzzash715a2",
"type": "tarball",
"url": "https://github.com/nix-community/poetry2nix/archive/c6f356183361493bc556138eca452878b651b90c.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
}
}

174
nix/sources.nix Normal file
View File

@ -0,0 +1,174 @@
# This file has been generated by Niv.
let
#
# The fetchers. fetch_<type> fetches specs of type <type>.
#
fetch_file = pkgs: name: spec:
let
name' = sanitizeName name + "-src";
in
if spec.builtin or true then
builtins_fetchurl { inherit (spec) url sha256; name = name'; }
else
pkgs.fetchurl { inherit (spec) url sha256; name = name'; };
fetch_tarball = pkgs: name: spec:
let
name' = sanitizeName name + "-src";
in
if spec.builtin or true then
builtins_fetchTarball { name = name'; inherit (spec) url sha256; }
else
pkgs.fetchzip { name = name'; inherit (spec) url sha256; };
fetch_git = name: spec:
let
ref =
if spec ? ref then spec.ref else
if spec ? branch then "refs/heads/${spec.branch}" else
if spec ? tag then "refs/tags/${spec.tag}" else
abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!";
in
builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; };
fetch_local = spec: spec.path;
fetch_builtin-tarball = name: throw
''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`.
$ niv modify ${name} -a type=tarball -a builtin=true'';
fetch_builtin-url = name: throw
''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`.
$ niv modify ${name} -a type=file -a builtin=true'';
#
# Various helpers
#
# https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695
sanitizeName = name:
(
concatMapStrings (s: if builtins.isList s then "-" else s)
(
builtins.split "[^[:alnum:]+._?=-]+"
((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name)
)
);
# The set of packages used when specs are fetched using non-builtins.
mkPkgs = sources: system:
let
sourcesNixpkgs =
import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; };
hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
hasThisAsNixpkgsPath = <nixpkgs> == ./.;
in
if builtins.hasAttr "nixpkgs" sources
then sourcesNixpkgs
else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then
import <nixpkgs> {}
else
abort
''
Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
add a package called "nixpkgs" to your sources.json.
'';
# The actual fetching function.
fetch = pkgs: name: spec:
if ! builtins.hasAttr "type" spec then
abort "ERROR: niv spec ${name} does not have a 'type' attribute"
else if spec.type == "file" then fetch_file pkgs name spec
else if spec.type == "tarball" then fetch_tarball pkgs name spec
else if spec.type == "git" then fetch_git name spec
else if spec.type == "local" then fetch_local spec
else if spec.type == "builtin-tarball" then fetch_builtin-tarball name
else if spec.type == "builtin-url" then fetch_builtin-url name
else
abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
# If the environment variable NIV_OVERRIDE_${name} is set, then use
# the path directly as opposed to the fetched source.
replace = name: drv:
let
saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name;
ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}";
in
if ersatz == "" then drv else
# this turns the string into an actual Nix path (for both absolute and
# relative paths)
if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}";
# Ports of functions for older nix versions
# a Nix version of mapAttrs if the built-in doesn't exist
mapAttrs = builtins.mapAttrs or (
f: set: with builtins;
listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))
);
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1);
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
concatMapStrings = f: list: concatStrings (map f list);
concatStrings = builtins.concatStringsSep "";
# https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331
optionalAttrs = cond: as: if cond then as else {};
# fetchTarball version that is compatible between all the versions of Nix
builtins_fetchTarball = { url, name ? null, sha256 }@attrs:
let
inherit (builtins) lessThan nixVersion fetchTarball;
in
if lessThan nixVersion "1.12" then
fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
else
fetchTarball attrs;
# fetchurl version that is compatible between all the versions of Nix
builtins_fetchurl = { url, name ? null, sha256 }@attrs:
let
inherit (builtins) lessThan nixVersion fetchurl;
in
if lessThan nixVersion "1.12" then
fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
else
fetchurl attrs;
# Create the final "sources" from the config
mkSources = config:
mapAttrs (
name: spec:
if builtins.hasAttr "outPath" spec
then abort
"The values in sources.json should not have an 'outPath' attribute"
else
spec // { outPath = replace name (fetch config.pkgs name spec); }
) config.sources;
# The "config" used by the fetchers
mkConfig =
{ sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null
, sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile)
, system ? builtins.currentSystem
, pkgs ? mkPkgs sources system
}: rec {
# The sources, i.e. the attribute set of spec name to spec
inherit sources;
# The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
inherit pkgs;
};
in
mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); }

583
poetry.lock generated Normal file
View File

@ -0,0 +1,583 @@
[[package]]
name = "certifi"
version = "2019.11.28"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "cffi"
version = "1.14.5"
description = "Foreign Function Interface for Python calling C code."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
pycparser = "*"
[[package]]
name = "chardet"
version = "3.0.4"
description = "Universal encoding detector for Python 2 and 3"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "cryptography"
version = "3.4.6"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
cffi = ">=1.12"
[package.extras]
docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
sdist = ["setuptools-rust (>=0.11.4)"]
ssh = ["bcrypt (>=3.1.5)"]
test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
[[package]]
name = "defusedxml"
version = "0.7.0rc2"
description = "XML bomb protection for Python stdlib modules"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "django"
version = "1.11.28"
description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
pytz = "*"
[package.extras]
argon2 = ["argon2-cffi (>=16.1.0)"]
bcrypt = ["bcrypt"]
[[package]]
name = "django-appconf"
version = "1.0.3"
description = "A helper class for handling configuration defaults of packaged apps gracefully."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
django = "*"
six = "*"
[[package]]
name = "django-flat-responsive"
version = "2.0"
description = "An extension for Django admin that makes interface mobile friendly."
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "django-hstore"
version = "1.5a0"
description = "PostgreSQL HStore support for Django"
category = "main"
optional = false
python-versions = "*"
develop = false
[package.source]
type = "git"
url = "https://github.com/djangonauts/django-hstore.git"
reference = "61427e474cb2f4be8fdfce225d78a5330bc77eb0"
resolved_reference = "61427e474cb2f4be8fdfce225d78a5330bc77eb0"
[[package]]
name = "django-markdown2"
version = "0.3.1"
description = "This is a simple app, which supplies a single template tag for markdown markup."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
markdown2 = "*"
[[package]]
name = "django-select2"
version = "6.3.1-pbr-devdep"
description = "Select2 option fields for Django."
category = "main"
optional = false
python-versions = "*"
develop = false
[package.dependencies]
django-appconf = ">=0.6.0"
pbr = "*"
pytest-runner = "*"
[package.source]
type = "git"
url = "https://github.com/drozdziak1/django-select2.git"
reference = "038a763d9caab00be2d583675edf8a87618af5c7"
resolved_reference = "038a763d9caab00be2d583675edf8a87618af5c7"
[[package]]
name = "django-tree"
version = "0.1.0"
description = "Fast and easy tree structures."
category = "main"
optional = false
python-versions = "*"
develop = false
[package.dependencies]
Django = ">=1.8,<1.12"
[package.source]
type = "git"
url = "https://github.com/d42/django-tree.git"
reference = "687c01c02d91cada9ca1912e34e482da9e73e27a"
resolved_reference = "687c01c02d91cada9ca1912e34e482da9e73e27a"
[[package]]
name = "djangorestframework"
version = "3.11.0"
description = "Web APIs for Django, made easy."
category = "main"
optional = false
python-versions = ">=3.5"
[package.dependencies]
django = ">=1.11"
[[package]]
name = "djangorestframework-hstore"
version = "1.3"
description = "Django Rest Framework tools for django-hstore"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
Django = ">=1.6.11"
django-hstore = ">=1.3.1"
djangorestframework = ">=3.1.1"
psycopg2 = ">=2.4.3"
[[package]]
name = "idna"
version = "2.8"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "markdown2"
version = "2.4.0"
description = "A fast and complete Python implementation of Markdown"
category = "main"
optional = false
python-versions = ">=3.5, <4"
[[package]]
name = "oauthlib"
version = "3.1.0"
description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.extras]
rsa = ["cryptography"]
signals = ["blinker"]
signedtoken = ["cryptography", "pyjwt (>=1.0.0)"]
[[package]]
name = "pbr"
version = "5.5.1"
description = "Python Build Reasonableness"
category = "main"
optional = false
python-versions = ">=2.6"
[[package]]
name = "pillow"
version = "6.2.1"
description = "Python Imaging Library (Fork)"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "psycopg2"
version = "2.8.4"
description = "psycopg2 - Python-PostgreSQL Database Adapter"
category = "main"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
[[package]]
name = "pycparser"
version = "2.20"
description = "C parser in Python"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "pyjwt"
version = "2.0.1"
description = "JSON Web Token implementation in Python"
category = "main"
optional = false
python-versions = ">=3.6"
[package.extras]
crypto = ["cryptography (>=3.3.1,<4.0.0)"]
dev = ["sphinx", "sphinx-rtd-theme", "zope.interface", "cryptography (>=3.3.1,<4.0.0)", "pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)", "mypy", "pre-commit"]
docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
tests = ["pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)"]
[[package]]
name = "pytest-runner"
version = "5.3.0"
description = "Invoke py.test as distutils command with dependency resolution"
category = "main"
optional = false
python-versions = ">=3.6"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "pytest-virtualenv", "pytest-black (>=0.3.7)", "pytest-mypy"]
[[package]]
name = "python3-openid"
version = "3.2.0"
description = "OpenID support for modern servers and consumers."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
defusedxml = "*"
[package.extras]
mysql = ["mysql-connector-python"]
postgresql = ["psycopg2"]
[[package]]
name = "pytz"
version = "2021.1"
description = "World timezone definitions, modern and historical"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "requests"
version = "2.22.0"
description = "Python HTTP for Humans."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.dependencies]
certifi = ">=2017.4.17"
chardet = ">=3.0.2,<3.1.0"
idna = ">=2.5,<2.9"
urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
[package.extras]
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"]
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
[[package]]
name = "requests-oauthlib"
version = "1.3.0"
description = "OAuthlib authentication support for Requests."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies]
oauthlib = ">=3.0.0"
requests = ">=2.0.0"
[package.extras]
rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
[[package]]
name = "six"
version = "1.15.0"
description = "Python 2 and 3 compatibility utilities"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "social-auth-app-django"
version = "3.1.0"
description = "Python Social Authentication, Django integration."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
six = "*"
social-auth-core = ">=1.2.0"
[[package]]
name = "social-auth-core"
version = "4.0.3"
description = "Python social authentication made simple."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
cryptography = ">=1.4"
defusedxml = {version = ">=0.5.0rc1", markers = "python_version >= \"3.0\""}
oauthlib = ">=1.0.3"
PyJWT = ">=2.0.0"
python3-openid = {version = ">=3.0.10", markers = "python_version >= \"3.0\""}
requests = ">=2.9.1"
requests-oauthlib = ">=0.6.1"
[package.extras]
all = ["python-jose (>=3.0.0)", "pyjwt (>=1.7.1)", "python3-saml (>=1.2.1)", "cryptography (>=2.1.1)"]
allpy3 = ["python-jose (>=3.0.0)", "pyjwt (>=1.7.1)", "python3-saml (>=1.2.1)", "cryptography (>=2.1.1)", "defusedxml (>=0.5.0rc1)", "python3-openid (>=3.0.10)"]
azuread = ["cryptography (>=2.1.1)"]
openidconnect = ["python-jose (>=3.0.0)", "pyjwt (>=1.7.1)"]
saml = ["python3-saml (>=1.2.1)"]
[[package]]
name = "urllib3"
version = "1.25.8"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
[package.extras]
brotli = ["brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "e96da617856b34f16f3f486aece93c33e4781e5900bc685976681848d55b1dd3"
[metadata.files]
certifi = [
{file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"},
{file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"},
]
cffi = [
{file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"},
{file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"},
{file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"},
{file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"},
{file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"},
{file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"},
{file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"},
{file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"},
{file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"},
{file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"},
{file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"},
{file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"},
{file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"},
{file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"},
{file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"},
{file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"},
{file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"},
{file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"},
{file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"},
{file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"},
{file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"},
{file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"},
{file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"},
{file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"},
{file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"},
{file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"},
{file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"},
{file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"},
{file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"},
{file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"},
{file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"},
{file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"},
{file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"},
{file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"},
{file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"},
{file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"},
{file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"},
]
chardet = [
{file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
{file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
]
cryptography = [
{file = "cryptography-3.4.6-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799"},
{file = "cryptography-3.4.6-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:4169a27b818de4a1860720108b55a2801f32b6ae79e7f99c00d79f2a2822eeb7"},
{file = "cryptography-3.4.6-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:93cfe5b7ff006de13e1e89830810ecbd014791b042cbe5eec253be11ac2b28f3"},
{file = "cryptography-3.4.6-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:5ecf2bcb34d17415e89b546dbb44e73080f747e504273e4d4987630493cded1b"},
{file = "cryptography-3.4.6-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:fec7fb46b10da10d9e1d078d1ff8ed9e05ae14f431fdbd11145edd0550b9a964"},
{file = "cryptography-3.4.6-cp36-abi3-win32.whl", hash = "sha256:df186fcbf86dc1ce56305becb8434e4b6b7504bc724b71ad7a3239e0c9d14ef2"},
{file = "cryptography-3.4.6-cp36-abi3-win_amd64.whl", hash = "sha256:66b57a9ca4b3221d51b237094b0303843b914b7d5afd4349970bb26518e350b0"},
{file = "cryptography-3.4.6-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:066bc53f052dfeda2f2d7c195cf16fb3e5ff13e1b6b7415b468514b40b381a5b"},
{file = "cryptography-3.4.6-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:600cf9bfe75e96d965509a4c0b2b183f74a4fa6f5331dcb40fb7b77b7c2484df"},
{file = "cryptography-3.4.6-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:0923ba600d00718d63a3976f23cab19aef10c1765038945628cd9be047ad0336"},
{file = "cryptography-3.4.6-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:9e98b452132963678e3ac6c73f7010fe53adf72209a32854d55690acac3f6724"},
{file = "cryptography-3.4.6.tar.gz", hash = "sha256:2d32223e5b0ee02943f32b19245b61a62db83a882f0e76cc564e1cec60d48f87"},
]
defusedxml = [
{file = "defusedxml-0.7.0rc2-py2.py3-none-any.whl", hash = "sha256:a034ed41d090f2348e4ac209fb3796b54728f7bf5ba326b2c5c599c293c80659"},
{file = "defusedxml-0.7.0rc2.tar.gz", hash = "sha256:24173aa6820e52524921533ff5af04bf5f0096f21c1628195ae59b44731acc5c"},
]
django = [
{file = "Django-1.11.28-py2.py3-none-any.whl", hash = "sha256:a3b01cdff845a43830d7ccacff55e0b8ff08305a4cbf894517a686e53ba3ad2d"},
{file = "Django-1.11.28.tar.gz", hash = "sha256:b33ce35f47f745fea6b5aa3cf3f4241069803a3712d423ac748bd673a39741eb"},
]
django-appconf = [
{file = "django-appconf-1.0.3.tar.gz", hash = "sha256:35f13ca4d567f132b960e2cd4c832c2d03cb6543452d34e29b7ba10371ba80e3"},
{file = "django_appconf-1.0.3-py2.py3-none-any.whl", hash = "sha256:c98a7af40062e996b921f5962a1c4f3f0c979fa7885f7be4710cceb90ebe13a6"},
]
django-flat-responsive = [
{file = "django-flat-responsive-2.0.tar.gz", hash = "sha256:451caa2700c541b52fb7ce2d34d3d8dee9e980cf29f5463bc8a8c6256a1a6474"},
]
django-hstore = []
django-markdown2 = [
{file = "django-markdown2-0.3.1.tar.gz", hash = "sha256:f04748d56788f6291d8d35f0e53a58530e1c0b34981b046883b319b64c8117e2"},
]
django-select2 = []
django-tree = []
djangorestframework = [
{file = "djangorestframework-3.11.0-py3-none-any.whl", hash = "sha256:05809fc66e1c997fd9a32ea5730d9f4ba28b109b9da71fccfa5ff241201fd0a4"},
{file = "djangorestframework-3.11.0.tar.gz", hash = "sha256:e782087823c47a26826ee5b6fa0c542968219263fb3976ec3c31edab23a4001f"},
]
djangorestframework-hstore = [
{file = "djangorestframework-hstore-1.3.tar.gz", hash = "sha256:2f9516a26f251ab007460573f13c76e5420528cf2bb91637811842e25c6180c6"},
{file = "djangorestframework_hstore-1.3-py2.py3-none-any.whl", hash = "sha256:4825ae8aff299aa3dc5a137b8bd9ca3efe0580fcb5c3699eb7e1a1030b17465d"},
]
idna = [
{file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"},
{file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"},
]
markdown2 = [
{file = "markdown2-2.4.0-py2.py3-none-any.whl", hash = "sha256:8d4ef4a2d090c99532069c4611a9a2b9bea6ae1fa29b6c3727c95d1e31a8f6c5"},
{file = "markdown2-2.4.0.tar.gz", hash = "sha256:28d769f0e544e6f68f684f01e9b186747b079a6927d9ca77ebc8c640a2829b1b"},
]
oauthlib = [
{file = "oauthlib-3.1.0-py2.py3-none-any.whl", hash = "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"},
{file = "oauthlib-3.1.0.tar.gz", hash = "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889"},
]
pbr = [
{file = "pbr-5.5.1-py2.py3-none-any.whl", hash = "sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00"},
{file = "pbr-5.5.1.tar.gz", hash = "sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9"},
]
pillow = [
{file = "Pillow-6.2.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:4ac6148008c169603070c092e81f88738f1a0c511e07bd2bb0f9ef542d375da9"},
{file = "Pillow-6.2.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:4aad1b88933fd6dc2846552b89ad0c74ddbba2f0884e2c162aa368374bf5abab"},
{file = "Pillow-6.2.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c710fcb7ee32f67baf25aa9ffede4795fd5d93b163ce95fdc724383e38c9df96"},
{file = "Pillow-6.2.1-cp27-cp27m-win32.whl", hash = "sha256:e9a3edd5f714229d41057d56ac0f39ad9bdba6767e8c888c951869f0bdd129b0"},
{file = "Pillow-6.2.1-cp27-cp27m-win_amd64.whl", hash = "sha256:b1ae48d87f10d1384e5beecd169c77502fcc04a2c00a4c02b85f0a94b419e5f9"},
{file = "Pillow-6.2.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:a423c2ea001c6265ed28700df056f75e26215fd28c001e93ef4380b0f05f9547"},
{file = "Pillow-6.2.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:9f5529fc02009f96ba95bea48870173426879dc19eec49ca8e08cd63ecd82ddb"},
{file = "Pillow-6.2.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:5cc901c2ab9409b4b7ac7b5bcc3e86ac14548627062463da0af3b6b7c555a871"},
{file = "Pillow-6.2.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c6414f6aad598364aaf81068cabb077894eb88fed99c6a65e6e8217bab62ae7a"},
{file = "Pillow-6.2.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:384b12c9aa8ef95558abdcb50aada56d74bc7cc131dd62d28c2d0e4d3aadd573"},
{file = "Pillow-6.2.1-cp35-cp35m-win32.whl", hash = "sha256:248cffc168896982f125f5c13e9317c059f74fffdb4152893339f3be62a01340"},
{file = "Pillow-6.2.1-cp35-cp35m-win_amd64.whl", hash = "sha256:285edafad9bc60d96978ed24d77cdc0b91dace88e5da8c548ba5937c425bca8b"},
{file = "Pillow-6.2.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:846fa202bd7ee0f6215c897a1d33238ef071b50766339186687bd9b7a6d26ac5"},
{file = "Pillow-6.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:7ce80c0a65a6ea90ef9c1f63c8593fcd2929448613fc8da0adf3e6bfad669d08"},
{file = "Pillow-6.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:e0697b826da6c2472bb6488db4c0a7fa8af0d52fa08833ceb3681358914b14e5"},
{file = "Pillow-6.2.1-cp36-cp36m-win32.whl", hash = "sha256:047d9473cf68af50ac85f8ee5d5f21a60f849bc17d348da7fc85711287a75031"},
{file = "Pillow-6.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:83792cb4e0b5af480588601467c0764242b9a483caea71ef12d22a0d0d6bdce2"},
{file = "Pillow-6.2.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:c9e5ffb910b14f090ac9c38599063e354887a5f6d7e6d26795e916b4514f2c1a"},
{file = "Pillow-6.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4deb1d2a45861ae6f0b12ea0a786a03d19d29edcc7e05775b85ec2877cb54c5e"},
{file = "Pillow-6.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0f66dc6c8a3cc319561a633b6aa82c44107f12594643efa37210d8c924fc1c71"},
{file = "Pillow-6.2.1-cp37-cp37m-win32.whl", hash = "sha256:59aa2c124df72cc75ed72c8d6005c442d4685691a30c55321e00ed915ad1a291"},
{file = "Pillow-6.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6c1db03e8dff7b9f955a0fb9907eb9ca5da75b5ce056c0c93d33100a35050281"},
{file = "Pillow-6.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:12c9169c4e8fe0a7329e8658c7e488001f6b4c8e88740e76292c2b857af2e94c"},
{file = "Pillow-6.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:27faf0552bf8c260a5cee21a76e031acaea68babb64daf7e8f2e2540745082aa"},
{file = "Pillow-6.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:809c19241c14433c5d6135e1b6c72da4e3b56d5c865ad5736ab99af8896b8f41"},
{file = "Pillow-6.2.1-cp38-cp38-win32.whl", hash = "sha256:ac4428094b42907aba5879c7c000d01c8278d451a3b7cccd2103e21f6397ea75"},
{file = "Pillow-6.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:38950b3a707f6cef09cd3cbb142474357ad1a985ceb44d921bdf7b4647b3e13e"},
{file = "Pillow-6.2.1-pp272-pypy_41-win32.whl", hash = "sha256:5a47d2123a9ec86660fe0e8d0ebf0aa6bc6a17edc63f338b73ea20ba11713f12"},
{file = "Pillow-6.2.1-pp372-pp372-win32.whl", hash = "sha256:c7be4b8a09852291c3c48d3c25d1b876d2494a0a674980089ac9d5e0d78bd132"},
{file = "Pillow-6.2.1.tar.gz", hash = "sha256:bf4e972a88f8841d8fdc6db1a75e0f8d763e66e3754b03006cbc3854d89f1cb1"},
]
psycopg2 = [
{file = "psycopg2-2.8.4-cp27-cp27m-win32.whl", hash = "sha256:72772181d9bad1fa349792a1e7384dde56742c14af2b9986013eb94a240f005b"},
{file = "psycopg2-2.8.4-cp27-cp27m-win_amd64.whl", hash = "sha256:893c11064b347b24ecdd277a094413e1954f8a4e8cdaf7ffbe7ca3db87c103f0"},
{file = "psycopg2-2.8.4-cp34-cp34m-win32.whl", hash = "sha256:9ab75e0b2820880ae24b7136c4d230383e07db014456a476d096591172569c38"},
{file = "psycopg2-2.8.4-cp34-cp34m-win_amd64.whl", hash = "sha256:b0845e3bdd4aa18dc2f9b6fb78fbd3d9d371ad167fd6d1b7ad01c0a6cdad4fc6"},
{file = "psycopg2-2.8.4-cp35-cp35m-win32.whl", hash = "sha256:ef6df7e14698e79c59c7ee7cf94cd62e5b869db369ed4b1b8f7b729ea825712a"},
{file = "psycopg2-2.8.4-cp35-cp35m-win_amd64.whl", hash = "sha256:965c4c93e33e6984d8031f74e51227bd755376a9df6993774fd5b6fb3288b1f4"},
{file = "psycopg2-2.8.4-cp36-cp36m-win32.whl", hash = "sha256:ed686e5926929887e2c7ae0a700e32c6129abb798b4ad2b846e933de21508151"},
{file = "psycopg2-2.8.4-cp36-cp36m-win_amd64.whl", hash = "sha256:dca2d7203f0dfce8ea4b3efd668f8ea65cd2b35112638e488a4c12594015f67b"},
{file = "psycopg2-2.8.4-cp37-cp37m-win32.whl", hash = "sha256:8396be6e5ff844282d4d49b81631772f80dabae5658d432202faf101f5283b7c"},
{file = "psycopg2-2.8.4-cp37-cp37m-win_amd64.whl", hash = "sha256:47fc642bf6f427805daf52d6e52619fe0637648fe27017062d898f3bf891419d"},
{file = "psycopg2-2.8.4-cp38-cp38-win32.whl", hash = "sha256:4212ca404c4445dc5746c0d68db27d2cbfb87b523fe233dc84ecd24062e35677"},
{file = "psycopg2-2.8.4-cp38-cp38-win_amd64.whl", hash = "sha256:92a07dfd4d7c325dd177548c4134052d4842222833576c8391aab6f74038fc3f"},
{file = "psycopg2-2.8.4.tar.gz", hash = "sha256:f898e5cc0a662a9e12bde6f931263a1bbd350cfb18e1d5336a12927851825bb6"},
]
pycparser = [
{file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
{file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
]
pyjwt = [
{file = "PyJWT-2.0.1-py3-none-any.whl", hash = "sha256:b70b15f89dc69b993d8a8d32c299032d5355c82f9b5b7e851d1a6d706dffe847"},
{file = "PyJWT-2.0.1.tar.gz", hash = "sha256:a5c70a06e1f33d81ef25eecd50d50bd30e34de1ca8b2b9fa3fe0daaabcf69bf7"},
]
pytest-runner = [
{file = "pytest-runner-5.3.0.tar.gz", hash = "sha256:ca3f58ff4957e8be6c54c55d575b235725cbbcf4dc0d5091c29c6444cfc8a5fe"},
{file = "pytest_runner-5.3.0-py3-none-any.whl", hash = "sha256:448959d9ada752de2b369cf05c1c0f9e6d2027e7d32441187c16c24c1d4d6e77"},
]
python3-openid = [
{file = "python3-openid-3.2.0.tar.gz", hash = "sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf"},
{file = "python3_openid-3.2.0-py3-none-any.whl", hash = "sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b"},
]
pytz = [
{file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"},
{file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"},
]
requests = [
{file = "requests-2.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"},
{file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"},
]
requests-oauthlib = [
{file = "requests-oauthlib-1.3.0.tar.gz", hash = "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"},
{file = "requests_oauthlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d"},
{file = "requests_oauthlib-1.3.0-py3.7.egg", hash = "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"},
]
six = [
{file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
{file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
]
social-auth-app-django = [
{file = "social-auth-app-django-3.1.0.tar.gz", hash = "sha256:6d0dd18c2d9e71ca545097d57b44d26f59e624a12833078e8e52f91baf849778"},
{file = "social_auth_app_django-3.1.0-py2-none-any.whl", hash = "sha256:f151396e5b16e2eee12cd2e211004257826ece24fc4ae97a147df386c1cd7082"},
{file = "social_auth_app_django-3.1.0-py3-none-any.whl", hash = "sha256:9237e3d7b6f6f59494c3b02e0cce6efc69c9d33ad9d1a064e3b2318bcbe89ae3"},
]
social-auth-core = [
{file = "social-auth-core-4.0.3.tar.gz", hash = "sha256:694eb355825cd72d3346afb816dd899493be1a8ee7405945d2e989cabed10cf2"},
{file = "social_auth_core-4.0.3-py3-none-any.whl", hash = "sha256:567b1f1bb1912e2c3153df888b48ba883dabdfe72f031e8cae4d404f61745c21"},
]
urllib3 = [
{file = "urllib3-1.25.8-py2.py3-none-any.whl", hash = "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc"},
{file = "urllib3-1.25.8.tar.gz", hash = "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"},
]

30
pyproject.toml Normal file
View File

@ -0,0 +1,30 @@
[tool.poetry]
name = "spejstore"
version = "0.1.0"
description = "Your mom's basement was never this organized"
authors = ["Warszawski Hackerspace <kontakt@hackerspace.pl>"]
[tool.poetry.dependencies]
python = "^3.8"
certifi = "2019.11.28"
chardet = "3.0.4"
Django = "1.11.28"
django-appconf = "1.0.3"
django-flat-responsive = "2.0"
social-auth-app-django = "3.1.0"
django-select2 = {git = "https://github.com/drozdziak1/django-select2.git", rev = "038a763d9caab00be2d583675edf8a87618af5c7"}
djangorestframework = "3.11.0"
Pillow = "6.2.1"
psycopg2 = "2.8.4"
djangorestframework-hstore = "1.3"
requests = "2.22.0"
urllib3 = "1.25.8"
django_markdown2 = "0.3.1"
django-hstore = {git = "https://github.com/djangonauts/django-hstore.git", rev = "61427e474cb2f4be8fdfce225d78a5330bc77eb0"}
django-tree = {git = "https://github.com/d42/django-tree.git", rev = "687c01c02d91cada9ca1912e34e482da9e73e27a"}
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"

View File

@ -1,16 +1,16 @@
certifi==2017.4.17
chardet==3.0.3
Django==1.11.15
certifi==2019.11.28
chardet==3.0.4
Django==1.11.28
git+https://github.com/djangonauts/django-hstore@61427e474cb2f4be8fdfce225d78a5330bc77eb0#egg=django-hstore
git+https://github.com/d42/django-tree@687c01c02d91cada9ca1912e34e482da9e73e27a#egg=django-tree
django-appconf==1.0.2
django-appconf==1.0.3
django-flat-responsive==2.0
social-auth-app-django==2.1.0
social-auth-app-django==3.1.0
Django-Select2==6.3.1
djangorestframework==3.5.4
Pillow==3.3.1
psycopg2==2.7.5
djangorestframework==3.11.0
Pillow==6.2.1
psycopg2==2.8.4
djangorestframework-hstore==1.3
requests==2.16.5
urllib3==1.21.1
django_markdown2==0.3.0
requests==2.22.0
urllib3==1.25.8
django_markdown2==0.3.1

24
shell.nix Normal file
View File

@ -0,0 +1,24 @@
{ sources ? import ./nix/sources.nix
, poetry2nix-olay ? import
"${sources.poetry2nix}/overlay.nix"
, nixpkgs ? sources.nixpkgs
}:
let
common-deps = import ./common-deps.nix { inherit sources; };
labelmaker-common-deps = import ./labelmaker/common-deps.nix { inherit sources; };
pkgs = import nixpkgs { overlays = [ poetry2nix-olay ]; };
spejstore-env = pkgs.poetry2nix.mkPoetryEnv {
projectDir = ./.;
};
in
pkgs.mkShell {
DOCKER_BUILDKIT = 1;
COMPOSE_DOCKER_CLI_BUILD = 1;
nativeBuildInputs = with pkgs; [
spejstore-env
poetry
bundix
] ++ common-deps ++ labelmaker-common-deps;
}

20
spejstore.nix Normal file
View File

@ -0,0 +1,20 @@
{ sources ? import ./nix/sources.nix
, poetry2nix-olay ? import
# ../poetry2nix/overlay.nix
"${sources.poetry2nix}/overlay.nix"
, nixpkgs ? sources.nixpkgs
}:
let
common-deps = import ./common-deps.nix { inherit sources; };
pkgs = import nixpkgs { overlays = [ poetry2nix-olay ]; };
src = ./.;
poetry-app = pkgs.poetry2nix.mkPoetryApplication {
projectDir = src;
};
dep-env = poetry-app.dependencyEnv;
manage-py = pkgs.writeScriptBin "manage-py" ''
cd ${src}
${dep-env}/bin/python manage.py $@
'';
in
manage-py

View File

@ -30,9 +30,18 @@ SECRET_KEY = env('SECRET_KEY', '#hjthi7_udsyt*9eeyb&nwgw5x=%pk_lnz3+u2tg9@=w3p1m
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = not PROD
if DEBUG:
import logging
logging.basicConfig(level=logging.DEBUG)
ALLOWED_HOSTS = env('ALLOWED_HOSTS', 'devinventory,inventory.waw.hackerspace.pl,i,inventory').split(',')
LOGIN_REDIRECT_URL = '/admin/'
# Hostname for label QR codes, must be on the allowed list
SHORT_HOST = env('SHORT_HOST', 'i')
if SHORT_HOST not in ALLOWED_HOSTS:
print(f'"{SHORT_HOST}" is not an allowed hostname (must be one of {ALLOWED_HOSTS}')
raise ValueError
# Application definition

View File

@ -1,6 +1,6 @@
from rest_framework import viewsets, generics, filters
from rest_framework.response import Response
from rest_framework.decorators import detail_route
from rest_framework.decorators import action
from rest_framework.permissions import AllowAny
from storage.models import Item, Label
@ -8,6 +8,8 @@ from storage.serializers import ItemSerializer, LabelSerializer
from django.http import Http404
from django.shortcuts import get_object_or_404
import logging
from storage.views import apply_smart_search
@ -31,13 +33,13 @@ class LabelViewSet(viewsets.ModelViewSet):
queryset = Label.objects
serializer_class = LabelSerializer
@detail_route(methods=['post'], permission_classes=[AllowAny])
@action(detail=True, methods=['post'], permission_classes=[AllowAny])
def print(self, request, pk):
quantity = min(int(request.query_params.get('quantity', 1)), 5)
obj = self.get_object()
for _ in range(quantity):
obj.print()
return obj
return Response()
class ItemViewSet(viewsets.ModelViewSet):
@ -72,31 +74,31 @@ class ItemViewSet(viewsets.ModelViewSet):
except Label.DoesNotExist:
raise Http404()
@detail_route(methods=['post'], permission_classes=[AllowAny])
@action(detail=True, methods=['post'])
def print(self, request, pk):
# todo: deduplicate
quantity = min(int(request.query_params.get('quantity', 1)), 5)
obj = self.get_object()
item = self.get_object()
for _ in range(quantity):
obj.print()
return obj
item.print()
return Response()
@detail_route()
@action(detail=True)
def children(self, request, pk):
item = self.get_object()
return Response(self.serializer_class(item.get_children().all(), many=True).data)
@detail_route()
@action(detail=True)
def ancestors(self, request, pk):
item = self.get_object()
return Response(self.serializer_class(item.get_ancestors().all(), many=True).data)
@detail_route()
@action(detail=True)
def descendants(self, request, pk):
item = self.get_object()
return Response(self.serializer_class(item.get_descendants().all(), many=True).data)
@detail_route()
@action(detail=True)
def siblings(self, request, pk):
item = self.get_object()
return Response(self.serializer_class(item.get_siblings().all(), many=True).data)

34
storage/labelmaker_api.py Normal file
View File

@ -0,0 +1,34 @@
from django.conf import settings
import requests
def labelmaker_print(name, qr_text, meta_text=''):
"""
Generic labelmaker printing, throws on request failure
"""
json_dict = {'name': name,
'qr_text': qr_text,
'meta_text': meta_text
}
print(f'JSON dict: {json_dict}')
resp = requests.post(
f'{settings.LABEL_API}/api/1/print',
json=json_dict)
resp.raise_for_status()
def labelmaker_render(name, qr_text, meta_text='', fmt='pdf'):
"""
Generic labelmaker rendering, throws on request failure. `fmt`
must be one of 'pdf', 'png'.
"""
json_dict = {'name': name,
'qr_text': qr_text,
'meta_text': meta_text
}
print(f'JSON dict: {json_dict}')
resp = requests.get(
f'{settings.LABEL_API}/api/1/render/{fmt}',
json=json_dict)
resp.raise_for_status()

View File

@ -1,7 +1,9 @@
from __future__ import unicode_literals
import uuid
import json
import logging
import re
import uuid
from django.db import models
from django.conf import settings
@ -12,6 +14,8 @@ from tree.models import TreeModelMixin
import requests
from labelmaker_api import labelmaker_print, labelmaker_render
STATES = (
('present', 'Present'),
@ -43,7 +47,8 @@ class Category(models.Model):
# Also ID zawierające część name
class Item(models.Model, TreeModelMixin):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
uuid = models.UUIDField(
primary_key=True, default=uuid.uuid4, editable=False)
parent = models.ForeignKey('self', null=True, blank=True)
path = PathField()
@ -51,11 +56,14 @@ class Item(models.Model, TreeModelMixin):
name = models.TextField()
description = models.TextField(blank=True, null=True)
state = models.CharField(max_length=31, choices=STATES, default=STATES[0][0])
state = models.CharField(
max_length=31, choices=STATES, default=STATES[0][0])
categories = models.ManyToManyField(Category, blank=True)
owner = models.ForeignKey(User, null=True, blank=True, related_name='owned_items')
owner = models.ForeignKey(
User, null=True, blank=True, related_name='owned_items')
taken_by = models.ForeignKey(User, null=True, blank=True, related_name='taken_items')
taken_by = models.ForeignKey(
User, null=True, blank=True, related_name='taken_items')
taken_on = models.DateTimeField(blank=True, null=True)
taken_until = models.DateTimeField(blank=True, null=True)
@ -65,7 +73,7 @@ class Item(models.Model, TreeModelMixin):
def short_id(self):
# let's just hope we never have 4 294 967 296 things :)
return str(self.pk)[:8] # collisions? what collisions?
return str(self.pk)[:8] # collisions? what collisions?
def __str__(self):
return '- ' * (self.get_level() or 0) + self.name
@ -74,10 +82,13 @@ class Item(models.Model, TreeModelMixin):
from django.urls import reverse
return reverse('item-display', kwargs={'pk': str(self.pk)})
def get_short_url(self):
return f'http://{settings.SHORT_HOST}/{self.short_id()}'
def get_or_create_label(self, **kwargs):
defaults = {
'id': re.sub('[^A-Z0-9]', '', self.name.upper())[:16],
}
}
defaults.update(kwargs)
@ -90,10 +101,10 @@ class Item(models.Model, TreeModelMixin):
return next((c for c in self.categories.all() if c.icon_id), None)
def print(self):
# todo: deduplicate
resp = requests.post(
'{}/api/1/print/{}'.format(settings.LABEL_API, self.short_id()))
resp.raise_for_status()
labelmaker_print(self.name,
self.get_short_url(),
f'owner: {self.owner.username}'
)
class Meta:
ordering = ('path',)
@ -112,13 +123,11 @@ class Label(models.Model):
item = models.ForeignKey(Item, related_name='labels')
style = models.CharField(max_length=32, choices=(
('basic_99012_v1', 'Basic Dymo 89x36mm label'),
), default='basic_99012_v1')
), default='basic_99012_v1')
created = models.DateTimeField(auto_now_add=True, blank=True)
def __str__(self):
return '{}'.format(self.id)
def print(self):
resp = requests.post(
'{}/api/1/print/{}'.format(settings.LABEL_API, self.id))
resp.raise_for_status()
self.item.print()