summary refs log tree commit diff
path: root/pkgs/applications/office/paperless/default.nix
blob: 1383986cf2ed73e354ee450fd52263866082803e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
{ stdenv
, lib
, fetchFromGitHub
, makeWrapper
, callPackage

, python3
, imagemagick7
, ghostscript
, optipng
, tesseract
, unpaper
}:

## Usage

# ${paperless}/bin/paperless wraps manage.py

# ${paperless}/share/paperless/setup-env.sh can be sourced from a
# shell script to setup a Paperless environment

# paperless.withConfig is a convenience function to setup a
# configured Paperless instance. (See ./withConfig.nix)

# For WSGI with gunicorn, use a shell script like this:
# let
#   pythonEnv = paperless.python.withPackages (ps: paperless.runtimePackages ++ [ ps.gunicorn ]);
# in
#   writers.writeBash "run-gunicorn" ''
#     source ${paperless}/share/paperless/setup-env.sh
#     PYTHONPATH=$paperlessSrc ${pythonEnv}/bin/gunicorn paperless.wsgi
#   ''

let
  paperless = stdenv.mkDerivation rec {
    pname = "paperless";
    version = "2.7.0";

    src = fetchFromGitHub {
      owner = "the-paperless-project";
      repo = "paperless";
      rev = version;
      sha256 = "0pkmyky1crjnsg7r0gfk0fadisfsgzlsq6afpz16wx4hp6yvkkf7";
    };

    nativeBuildInputs = [ makeWrapper ];

    doCheck = true;
    dontInstall = true;

    pythonEnv      = python.withPackages (_: runtimePackages);
    pythonCheckEnv = python.withPackages (_: (runtimePackages ++ checkPackages));

    unpackPhase = ''
      srcDir=$out/share/paperless
      mkdir -p $srcDir
      cp -r --no-preserve=mode $src/src/* $src/LICENSE $srcDir
    '';

    postPatch = ''
      # django-cors-headers 3.x requires a scheme for allowed hosts
      substituteInPlace $out/share/paperless/paperless/settings.py \
        --replace "localhost:8080" "http://localhost:8080"
    '';

    buildPhase = let
      # Paperless has explicit runtime checks that expect these binaries to be in PATH
      extraBin = lib.makeBinPath [ imagemagick7 ghostscript optipng tesseract unpaper ];
    in ''
      ${python.interpreter} -m compileall $srcDir

      makeWrapper $pythonEnv/bin/python $out/bin/paperless \
        --set PATH ${extraBin} --add-flags $out/share/paperless/manage.py

      # A shell snippet that can be sourced to setup a paperless env
      cat > $out/share/paperless/setup-env.sh <<EOF
      export PATH="$pythonEnv/bin:${extraBin}''${PATH:+:}$PATH"
      export paperlessSrc=$out/share/paperless
      EOF
    '';

    checkPhase = ''
      source $out/share/paperless/setup-env.sh
      tmpDir=$(realpath testsTmp)
      mkdir $tmpDir
      export HOME=$tmpDir
      export PAPERLESS_MEDIADIR=$tmpDir
      cd $paperlessSrc
      # Prevent tests from writing to the derivation output
      chmod -R -w $out
      # Disable cache to silence a pytest warning ("could not create cache")
      $pythonCheckEnv/bin/pytest -p no:cacheprovider
    '';

    passthru = {
      withConfig = callPackage ./withConfig.nix {};
      inherit python runtimePackages checkPackages tesseract;
    };

    meta = with lib; {
      description = "Scan, index, and archive all of your paper documents";
      homepage = https://github.com/the-paperless-project/paperless;
      license = licenses.gpl3;
      maintainers = [ maintainers.earvstedt ];
    };
  };

  python = python3.override {
    packageOverrides = self: super: {
      # Paperless only supports Django 2.0
      django = django_2_0 super;
      pyocr = pyocrWithUserTesseract super;
      # These are pre-release versions, hence they are private to this pkg
      django-filter = self.callPackage ./python-modules/django-filter.nix {};
      django-crispy-forms = self.callPackage ./python-modules/django-crispy-forms.nix {};
    };
  };

  django_2_0 = pyPkgs: pyPkgs.django_2_2.overrideDerivation (_: rec {
    pname = "Django";
    version = "2.0.12";
    name = "${pname}-${version}";
    src = pyPkgs.fetchPypi {
      inherit pname version;
      sha256 = "15s8z54k0gf9brnz06521bikm60ddw5pn6v3nbvnl47j1jjsvwz2";
    };
  });

  runtimePackages = with python.pkgs; [
    dateparser
    dateutil
    django
    django-cors-headers
    django-crispy-forms
    django-filter
    django_extensions
    djangoql
    djangorestframework
    factory_boy
    filemagic
    fuzzywuzzy
    langdetect
    pdftotext
    pillow
    psycopg2
    pyocr
    python-dotenv
    python-gnupg
    pytz
    termcolor
  ] ++ (lib.optional stdenv.isLinux inotify-simple);

  checkPackages = with python.pkgs; [
    pytest
    pytest-django
    pytest-env
    pytest_xdist
  ];

  pyocrWithUserTesseract = pyPkgs:
    let
      pyocr = pyPkgs.pyocr.override { inherit tesseract; };
    in
      if pyocr.outPath == pyPkgs.pyocr.outPath then
        pyocr
      else
        # The user has provided a custom tesseract derivation that might be
        # missing some languages that are required for PyOCR's tests. Disable them to
        # avoid build errors.
        pyocr.overridePythonAttrs (attrs: {
          doCheck = false;
        });
in
  paperless