chellow 1712839751.0.0__py3-none-any.whl → 1712929217.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of chellow might be problematic. Click here for more details.

chellow/e/hh_importer.py CHANGED
@@ -8,13 +8,13 @@ import traceback
8
8
  from collections import defaultdict, deque
9
9
  from datetime import datetime as Datetime, timedelta as Timedelta
10
10
  from decimal import Decimal
11
- from io import TextIOWrapper
11
+ from io import StringIO, TextIOWrapper
12
+ from pathlib import Path
12
13
 
13
14
  import jinja2
14
15
 
15
- import paramiko
16
+ from paramiko import AutoAddPolicy, RSAKey, SFTPError, SSHClient
16
17
 
17
- import pysftp
18
18
 
19
19
  import requests
20
20
 
@@ -174,7 +174,7 @@ class HhImportTask(threading.Thread):
174
174
  self.log(f"Problem {e}")
175
175
  sess.rollback()
176
176
  self.is_error = True
177
- except Exception:
177
+ except BaseException:
178
178
  self.log(f"Unknown Exception {traceback.format_exc()}")
179
179
  sess.rollback()
180
180
  self.is_error = True
@@ -231,19 +231,19 @@ class HhImportTask(threading.Thread):
231
231
  fl = None
232
232
 
233
233
  for directory in directories:
234
- self.log("Checking the directory '" + directory + "'.")
234
+ self.log(f"Checking the directory '{directory}'.")
235
235
  try:
236
236
  last_import_key = last_import_keys[directory]
237
237
  except KeyError:
238
238
  last_import_key = last_import_keys[directory] = ""
239
239
 
240
240
  dir_path = home_path + "/" + directory
241
- ftp.cwd(dir_path)
241
+ ftp.chdir(dir_path)
242
242
  files = []
243
- for fname in ftp.nlst():
243
+ for fname in ftp.listdir():
244
244
  fpath = dir_path + "/" + fname
245
245
  try:
246
- ftp.cwd(fpath)
246
+ ftp.chdir(fpath)
247
247
  continue # directory
248
248
  except ftplib.error_perm:
249
249
  pass
@@ -266,7 +266,7 @@ class HhImportTask(threading.Thread):
266
266
  key, fpath = fl
267
267
  self.log(f"Attempting to download {fpath} with key {key}.")
268
268
  f = tempfile.TemporaryFile()
269
- ftp.retrbinary("RETR " + fpath, f.write)
269
+ ftp.retrbinary(f"RETR {fpath}", f.write)
270
270
  self.log("File downloaded successfully.")
271
271
  ftp.quit()
272
272
  self.log("Logged out.")
@@ -290,25 +290,30 @@ class HhImportTask(threading.Thread):
290
290
  contract = Contract.get_dc_by_id(sess, self.contract_id)
291
291
  contract.update_state(state)
292
292
  sess.commit()
293
- self.log("Finished loading '" + fpath)
293
+ self.log(f"Finished loading '{fpath}'")
294
294
  return True
295
295
 
296
296
  def sftp_handler(self, sess, properties, contract):
297
- host_name = properties["hostname"]
298
- user_name = properties["username"]
299
- password = properties["password"]
300
- try:
301
- port = properties["port"]
302
- except KeyError:
303
- port = None
297
+ hostname = properties["hostname"]
298
+ username = properties["username"]
299
+ password = properties.get("password")
300
+ private_key = properties.get("private_key")
301
+ pkey = None
302
+ if private_key is not None:
303
+ pkf = StringIO.StringIO(private_key)
304
+ pkf.seek(0)
305
+ pkey = RSAKey.from_private_key(pkf)
306
+
307
+ port = properties.get("port", 22)
304
308
  file_type = properties["file_type"]
305
309
  directories = properties["directories"]
310
+ known_hosts = properties["known_hosts"].strip()
306
311
  state = contract.make_state()
307
312
 
308
313
  try:
309
314
  last_import_keys = state["last_import_keys"]
310
315
  except KeyError:
311
- last_import_keys = state["last_import_keys"] = []
316
+ last_import_keys = state["last_import_keys"] = {}
312
317
 
313
318
  ct_now = ct_datetime_now()
314
319
  try:
@@ -323,36 +328,60 @@ class HhImportTask(threading.Thread):
323
328
  latest_imports = state["latest_imports"] = []
324
329
 
325
330
  sess.rollback()
326
- self.log(f"Connecting to sftp server at {host_name}:{port}.")
327
- cnopts = pysftp.CnOpts()
328
- cnopts.hostkeys = None
329
- ftp = pysftp.Connection(
330
- host_name, username=user_name, password=password, cnopts=cnopts
331
+ self.log(f"Connecting to sftp server at {hostname}:{port}.")
332
+
333
+ client = SSHClient()
334
+ client.set_missing_host_key_policy(AutoAddPolicy)
335
+
336
+ client.connect(
337
+ hostname,
338
+ port=port,
339
+ username=username,
340
+ password=password,
341
+ pkey=pkey,
342
+ key_filename=None,
343
+ timeout=120,
344
+ banner_timeout=120,
345
+ auth_timeout=120,
346
+ channel_timeout=120,
347
+ look_for_keys=False,
331
348
  )
332
- ftp.timeout = 120
333
- home_path = ftp.pwd
349
+ host_keys = client.get_host_keys()
350
+ with tempfile.TemporaryDirectory() as tempdir:
351
+ td = Path(tempdir)
352
+ hfname = td / "hosts"
353
+ host_keys.save(hfname)
354
+ with hfname.open() as hf:
355
+ host_keys_str = hf.read().strip()
356
+
357
+ if host_keys_str != known_hosts:
358
+ raise BadRequest(
359
+ f"The returned host keys {host_keys_str} don't match the known "
360
+ f"hosts {known_hosts} property"
361
+ )
362
+
363
+ ftp = client.open_sftp()
334
364
 
335
365
  f = None
336
366
 
337
367
  for directory in directories:
338
- self.log("Checking the directory '" + directory + "'.")
368
+ self.log(f"Checking the directory '{directory}'.")
339
369
  try:
340
370
  last_import_key = last_import_keys[directory]
341
371
  except KeyError:
342
372
  last_import_key = last_import_keys[directory] = ""
343
373
 
344
- dir_path = home_path + "/" + directory
345
- ftp.cwd(dir_path)
374
+ ftp.chdir(directory)
346
375
  files = []
347
376
  for attr in ftp.listdir_attr():
348
- fpath = dir_path + "/" + attr.filename
377
+ fpath = f"{directory}/{attr.filename}"
349
378
  try:
350
- ftp.cwd(fpath)
379
+ ftp.chdir(fpath)
351
380
  continue # directory
352
- except paramiko.SFTPError:
381
+ except SFTPError:
353
382
  pass
354
383
 
355
- key = str(attr.st_mtime) + "_" + attr.filename
384
+ key = f"{attr.st_mtime}_{attr.filename}"
356
385
  if key > last_import_key:
357
386
  files.append((key, fpath))
358
387
 
@@ -364,6 +393,7 @@ class HhImportTask(threading.Thread):
364
393
  if f is None:
365
394
  self.log("No new files found.")
366
395
  ftp.close()
396
+ client.close()
367
397
  self.log("Logged out.")
368
398
  return False
369
399
  else:
chellow/e/views.py CHANGED
@@ -105,7 +105,11 @@ from chellow.utils import (
105
105
  utc_datetime,
106
106
  utc_datetime_now,
107
107
  )
108
- from chellow.views import chellow_redirect as credirect, hx_redirect as chx_redirect
108
+ from chellow.views import (
109
+ chellow_redirect as credirect,
110
+ hx_redirect as chx_redirect,
111
+ requires_editor,
112
+ )
109
113
 
110
114
 
111
115
  def chellow_redirect(path, code=None):
@@ -1075,6 +1079,44 @@ def dc_contract_get(dc_contract_id):
1075
1079
  )
1076
1080
 
1077
1081
 
1082
+ @e.route("/dc_contracts/<int:dc_contract_id>/properties")
1083
+ @requires_editor
1084
+ def dc_contract_properties_get(dc_contract_id):
1085
+ contract = Contract.get_dc_by_id(g.sess, dc_contract_id)
1086
+ return render_template("dc_contract_properties.html", dc_contract=contract)
1087
+
1088
+
1089
+ @e.route("/dc_contracts/<int:dc_contract_id>/properties/edit")
1090
+ @requires_editor
1091
+ def dc_contract_properties_edit_get(dc_contract_id):
1092
+ dc_contract = Contract.get_dc_by_id(g.sess, dc_contract_id)
1093
+ return render_template("dc_contract_properties_edit.html", dc_contract=dc_contract)
1094
+
1095
+
1096
+ @e.route("/dc_contracts/<int:dc_contract_id>/properties/edit", methods=["POST"])
1097
+ def dc_contract_properties_edit_post(dc_contract_id):
1098
+ dc_contract = None
1099
+ try:
1100
+ dc_contract = Contract.get_dc_by_id(g.sess, dc_contract_id)
1101
+ properties = req_zish("properties")
1102
+ dc_contract.update(
1103
+ dc_contract.name, dc_contract.party, dc_contract.charge_script, properties
1104
+ )
1105
+ g.sess.commit()
1106
+ return chellow_redirect(f"/dc_contracts/{dc_contract.id}/properties", 303)
1107
+ except BadRequest as e:
1108
+ flash(e.description)
1109
+ if dc_contract is None:
1110
+ raise e
1111
+ else:
1112
+ return make_response(
1113
+ render_template(
1114
+ "dc_contract_properties_edit.html", dc_contract=dc_contract
1115
+ ),
1116
+ 400,
1117
+ )
1118
+
1119
+
1078
1120
  @e.route("/dc_contracts/<int:dc_contract_id>/edit")
1079
1121
  def dc_contract_edit_get(dc_contract_id):
1080
1122
  parties = (
@@ -1127,9 +1169,8 @@ def dc_contract_edit_post(contract_id):
1127
1169
  party_id = req_int("party_id")
1128
1170
  name = req_str("name")
1129
1171
  charge_script = req_str("charge_script")
1130
- properties = req_zish("properties")
1131
1172
  party = Party.get_by_id(g.sess, party_id)
1132
- contract.update(name, party, charge_script, properties)
1173
+ contract.update(name, party, charge_script, contract.properties)
1133
1174
  g.sess.commit()
1134
1175
  return chellow_redirect(f"/dc_contracts/{contract.id}", 303)
1135
1176
  except BadRequest as e:
@@ -66,6 +66,9 @@
66
66
  </table>
67
67
  <br>
68
68
  <ul>
69
+ <li>
70
+ <a href="/e/dc_contracts/{{dc_contract.id}}/properties">Properties</a>
71
+ </li>
69
72
  <li>
70
73
  <a href="/e/dc_batches?dc_contract_id={{dc_contract.id}}">Batches</a>
71
74
  </li>
@@ -99,10 +102,6 @@
99
102
 
100
103
  <pre>{{dc_contract.charge_script}}</pre>
101
104
 
102
- <h3>Properties</h3>
103
-
104
- <pre>{{dc_contract.properties }}</pre>
105
-
106
105
  <h3>State</h3>
107
106
 
108
107
  <pre>{{dc_contract.state }}</pre>
@@ -42,58 +42,10 @@
42
42
  {{input_textarea(
43
43
  'charge_script', dc_contract.charge_script, 40, 80)}}
44
44
 
45
- <label>Properties</label>
46
- {{ input_textarea(
47
- 'properties', dc_contract.properties, 40, 80) }}
48
-
49
45
  <input type="submit" value="Update">
50
46
  </fieldset>
51
47
  </form>
52
48
 
53
- <h4>Example</h4>
54
-
55
- <p>For the SFTP protocol:</p>
56
- <code>
57
- <pre>
58
- {
59
- "enabled": true,
60
- "protocol": "sftp",
61
- "file_type": ".df2",
62
- "hostname": "example.com",
63
- "username": "username",
64
- "password": "password",
65
- "directories": ["downloads1", "downloads2"]
66
- "mpan_map": { \\ Optional
67
- "99 0993 2821 985": null, \\ Ignore MPAN
68
- },
69
- }
70
- </pre>
71
- </code>
72
- <p>For the HTTPS protocol:</p>
73
- <code>
74
- <pre>
75
- {% raw %}
76
- {
77
- "enabled": true,
78
- "protocol": "https",
79
- "download_days": 8,
80
- "parser": "meniscus",
81
- "url_template": "https://data.example.com/?from={{chunk_start.strftime('%d/%m/%Y')}}&amp;to={{chunk_finish.strftime('%d/%m/%Y')}}",
82
- "url_values": {
83
- "99 4298 4729 917": {
84
- "name1": val1,
85
- "name2": val2,
86
- }
87
- }
88
- "mpan_map": { \\ Optional
89
- "99 0993 2821 985": null, \\ Ignore MPAN
90
- },
91
- }
92
-
93
- {% endraw %}
94
- </pre>
95
- </code>
96
-
97
49
  <form action="/e/dc_contracts/{{dc_contract.id}}/edit" method="post">
98
50
  <fieldset>
99
51
  <legend>Update State</legend>
@@ -0,0 +1,17 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}
4
+ &raquo; DC Contracts &raquo; {{dc_contract.name}} &raquo; Properties
5
+ {% endblock %}
6
+
7
+ {% block nav %}
8
+ <a href="/e/dc_contracts">DC Contracts</a> &raquo;
9
+ <a href="/e/dc_contracts/{{dc_contract.id}}">{{dc_contract.name}}</a> &raquo;
10
+ Properties [<a href="/e/dc_contracts/{{dc_contract.id}}/properties/edit">edit</a>]
11
+ {% endblock %}
12
+
13
+ {% block content %}
14
+
15
+ <pre>{{dc_contract.properties }}</pre>
16
+
17
+ {% endblock %}
@@ -0,0 +1,71 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}
4
+ &raquo; DC Contracts &raquo; {{dc_contract.name}} &raquo; Properties &raquo; Edit
5
+ {% endblock %}
6
+
7
+ {% block nav %}
8
+ <a href="/e/dc_contracts">DC Contracts</a> &raquo;
9
+ <a href="/e/dc_contracts/{{dc_contract.id}}">{{dc_contract.name}}</a> &raquo;
10
+ <a href="/e/dc_contracts/{{dc_contract.id}}/properties">Properties</a> &raquo;
11
+ Edit
12
+ {% endblock %}
13
+
14
+ {% block content %}
15
+
16
+ <form action="/e/dc_contracts/{{dc_contract.id}}/properties/edit" method="post">
17
+ <fieldset>
18
+ <legend>Update Properties</legend>
19
+
20
+ <label>Properties</label>
21
+ {{ input_textarea('properties', dc_contract.properties, 40, 80, show_pos=True) }}
22
+
23
+ <input type="submit" value="Update">
24
+ </fieldset>
25
+ </form>
26
+
27
+ <h4>Example</h4>
28
+
29
+ <p>For the SFTP protocol:</p>
30
+ <code>
31
+ <pre>
32
+ {
33
+ "enabled": true,
34
+ "protocol": "sftp",
35
+ "file_type": ".df2",
36
+ "hostname": "example.com",
37
+ "username": "username",
38
+ "password": "password", /* Remove if using private key */
39
+ "private_key": "private_key", /* Remove if using password */
40
+ "directories": ["downloads1", "downloads2"],
41
+ "mpan_map": { /* Optional */
42
+ "99 0993 2821 985": null, /* Ignore MPAN */
43
+ },
44
+ }
45
+ </pre>
46
+ </code>
47
+ <p>For the HTTPS protocol:</p>
48
+ <code>
49
+ <pre>
50
+ {% raw %}
51
+ {
52
+ "enabled": true,
53
+ "protocol": "https",
54
+ "download_days": 8,
55
+ "parser": "meniscus",
56
+ "url_template": "https://data.example.com/?from={{chunk_start.strftime('%d/%m/%Y')}}&amp;to={{chunk_finish.strftime('%d/%m/%Y')}}",
57
+ "url_values": {
58
+ "99 4298 4729 917": {
59
+ "name1": val1,
60
+ "name2": val2,
61
+ }
62
+ }
63
+ "mpan_map": { \\ Optional
64
+ "99 0993 2821 985": null, \\ Ignore MPAN
65
+ },
66
+ }
67
+
68
+ {% endraw %}
69
+ </pre>
70
+ </code>
71
+ {% endblock %}
chellow/views.py CHANGED
@@ -11,6 +11,7 @@ import types
11
11
  from collections import OrderedDict
12
12
  from datetime import datetime as Datetime
13
13
  from decimal import Decimal
14
+ from functools import wraps
14
15
  from importlib import import_module
15
16
  from io import DEFAULT_BUFFER_SIZE, StringIO
16
17
  from itertools import chain, islice
@@ -52,7 +53,7 @@ from sqlalchemy.exc import IntegrityError
52
53
  from sqlalchemy.orm import joinedload
53
54
  from sqlalchemy.orm.attributes import flag_modified
54
55
 
55
- from werkzeug.exceptions import BadRequest
56
+ from werkzeug.exceptions import BadRequest, Forbidden
56
57
 
57
58
  import chellow.bank_holidays
58
59
  import chellow.dloads
@@ -147,6 +148,17 @@ def hx_redirect(path, status=None):
147
148
  return res
148
149
 
149
150
 
151
+ def requires_editor(f):
152
+ @wraps(f)
153
+ def decorated_function(*args, **kwargs):
154
+ if g.user.user_role.code == "editor":
155
+ return f(*args, **kwargs)
156
+ else:
157
+ raise Forbidden("You must be an editor to do this.")
158
+
159
+ return decorated_function
160
+
161
+
150
162
  @home.route("/configuration", methods=["GET"])
151
163
  def configuration():
152
164
  config = Contract.get_non_core_by_name(g.sess, "configuration")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: chellow
3
- Version: 1712839751.0.0
3
+ Version: 1712929217.0.0
4
4
  Summary: Web Application for checking UK energy bills.
5
5
  Project-URL: Homepage, https://github.com/WessexWater/chellow
6
6
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
@@ -11,13 +11,13 @@ Requires-Dist: flask-restx==1.2.0
11
11
  Requires-Dist: flask==2.3.3
12
12
  Requires-Dist: odio==0.0.22
13
13
  Requires-Dist: openpyxl==3.1.2
14
+ Requires-Dist: paramiko==3.4.0
14
15
  Requires-Dist: pep3143daemon==0.0.6
15
16
  Requires-Dist: pg8000==1.31.1
16
17
  Requires-Dist: pip>=9.0.1
17
18
  Requires-Dist: psutil==5.9.5
18
19
  Requires-Dist: pympler==1.0.1
19
20
  Requires-Dist: pypdf==3.17.0
20
- Requires-Dist: pysftp==0.2.9
21
21
  Requires-Dist: python-dateutil==2.8.2
22
22
  Requires-Dist: pytz==2022.6
23
23
  Requires-Dist: requests==2.31.0
@@ -11,7 +11,7 @@ chellow/proxy.py,sha256=cVXIktPlX3tQ1BYcwxq0nJXKE6r3DtFTtfFHPq55HaM,1351
11
11
  chellow/rate_server.py,sha256=vNuKzlr0lkAzZ4zG7zboStoo92_6iLKAxbw1yZ-ucII,5626
12
12
  chellow/testing.py,sha256=Od4HHH6pZrhJ_De118_F55RJEKmAvhUH2S24QE9qFQk,3635
13
13
  chellow/utils.py,sha256=32qPWEGzCPKPhSM7ztpzcR6ZG74sVZanScDIdK50Rq4,19308
14
- chellow/views.py,sha256=_vkjpoprfhIAzbaDlBYf2ewfsJXcthY_t9tfjrcMK9g,78520
14
+ chellow/views.py,sha256=YmczsGgUjNIWf3VZcH1oscOYolJhZ4j8mUBS8RewrHY,78838
15
15
  chellow/e/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  chellow/e/aahedc.py,sha256=d2usudp7KYWpU6Pk3fal5EQ47EbvkvKeaFGylnb3NWw,606
17
17
  chellow/e/bill_importer.py,sha256=y1bpn49xDwltj0PRFowWsjAVamcD8eyxUbCTdGxEZiE,7389
@@ -24,7 +24,7 @@ chellow/e/dno_rate_parser.py,sha256=5MYEbcYngh7n6Szn2h9zLTwQKXH-WHKy6c80PYwNLLQ,
24
24
  chellow/e/duos.py,sha256=ISTcNqe9KNjVNM2Qs8IBoQxnmSXOt5W_G7tZxp4T78M,28870
25
25
  chellow/e/elexon.py,sha256=ALhXS9Es7PV0z9ukPbIramn3cf3iLyFi-PMWPSm5iOs,5487
26
26
  chellow/e/energy_management.py,sha256=aXC2qlGt3FAODlNl_frWzVYAQrJLP8FFOiNX3m-QE_Y,12388
27
- chellow/e/hh_importer.py,sha256=nhmlWDNBm8U4salBud8ivdaDGF1lkY82NqQCrGuPYRI,20483
27
+ chellow/e/hh_importer.py,sha256=QBKdnT3Z4A6fsGqODE9bBGVdWde6Hu8OJfkXVhShQz0,21485
28
28
  chellow/e/hh_parser_bg_csv.py,sha256=W5SU2MSpa8BGA0VJw1JXF-IwbCNLFy8fe35yxLZ7gEw,2453
29
29
  chellow/e/hh_parser_df2.py,sha256=ynNNRMpLm9qs23tJjfm0vGSaAduXfPMw_7-WfQHXliQ,4209
30
30
  chellow/e/hh_parser_simple_csv.py,sha256=RN4QOLvTQeoPrpvXvQ9hkOBZnR5piybLfjCiSJdjpjs,2112
@@ -38,7 +38,7 @@ chellow/e/system_price.py,sha256=3cPWohHmmBI9v7fENLBzXQkpAeC3r0s5xV29Nmr6k_E,583
38
38
  chellow/e/tlms.py,sha256=kHAy2A2d1Mma7x4GdNDyrzscJd5DpJtDBYiZEg241Dw,8538
39
39
  chellow/e/tnuos.py,sha256=KoMPJDIXfE4zwhSDuywGF1ooxjTYLVjtF7BkSFm6X24,4158
40
40
  chellow/e/triad.py,sha256=S6LEMHvUKhAZe0-yfLIRciYDZ8IKMn1jh1TmmsbQD3s,13588
41
- chellow/e/views.py,sha256=p-zQa24HtNVLTHILqfiZ8UrixuljHSC163SrJuLQ3Ik,212590
41
+ chellow/e/views.py,sha256=8Rmm6XuNGXACFSSX_LpomyTC4oS2-nljRlisjEyUVJ4,213975
42
42
  chellow/e/bill_parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
43
  chellow/e/bill_parsers/activity_mop_stark_xlsx.py,sha256=UgWXDPzQkQghyj_lfgBqoSJpHB-t-qOdSaB8qY6GLog,4071
44
44
  chellow/e/bill_parsers/annual_mop_stark_xlsx.py,sha256=-HMoIfa_utXYKA44RuC0Xqv3vd2HLeQU_4P0iBUd3WA,4219
@@ -183,10 +183,12 @@ chellow/templates/e/dc_bill_add.html,sha256=73Sn_MKBsUuYYnDfUMCdX1Dul6GimMC9YXk6
183
183
  chellow/templates/e/dc_bill_edit.html,sha256=0GsN-ZIc4q-z_xs8igC2ZS6t4soo2SvB3qRA6iC-AuM,2707
184
184
  chellow/templates/e/dc_bill_import.html,sha256=NHjMSoFizvFQIaPWuVE3nTCtMDTzJB0XmH8jXfV1tiA,2188
185
185
  chellow/templates/e/dc_bill_imports.html,sha256=lCaUR47r9KPr0VrQhEvVEaKexM5R_nmkxtzgpWZ0e9s,2135
186
- chellow/templates/e/dc_contract.html,sha256=_fq_s69kmbrGlScYngRYm4cPdRgh2roMvSfvG0J4xzA,2326
187
- chellow/templates/e/dc_contract_edit.html,sha256=wKK2pXN78VfSJ6gjfNRkikRFkYTiKtGL3UeVFIYSRwc,3115
186
+ chellow/templates/e/dc_contract.html,sha256=b6DLDrGdzN9Rri56pFvfpE7QZKYXGHib2veYAztuHlg,2352
187
+ chellow/templates/e/dc_contract_edit.html,sha256=YraU0_MTBjmzBCMdsuvtIYm7K58hnDly5wUZ-6n8XzY,2148
188
188
  chellow/templates/e/dc_contract_hh_import.html,sha256=UODiBFNohb60MjH1w-9JW1JE0O9GR2uKPGw-lD7Da5g,848
189
189
  chellow/templates/e/dc_contract_hh_imports.html,sha256=eXFDGyzSgag4JRism81_p5yTzQOjCIXaVkQ8tl3dDcM,8172
190
+ chellow/templates/e/dc_contract_properties.html,sha256=2EmA91gYWZcTOHQ3PxXQrpTOb8dm0OGjwRWsW6qNI9s,455
191
+ chellow/templates/e/dc_contract_properties_edit.html,sha256=TGYcHTlWbk5WmjZPkdbHhRVZcOpEfhowCp2penZcGiE,1704
190
192
  chellow/templates/e/dc_contracts.html,sha256=v8czpEcMzndngfWNXIKXib6Xrz9OwJjQAmNQNrQ6Y08,1705
191
193
  chellow/templates/e/dc_contracts_add.html,sha256=2lrGrNqAoKp16OiMoNDNlJxBMW3QdSPfgEFheSg826s,710
192
194
  chellow/templates/e/dc_rate_script.html,sha256=gfKUHV2IMsTQT-cYfjl4aiEBqGEWSj3uW22UZpoyGq4,1219
@@ -361,6 +363,6 @@ chellow/templates/g/supply_note_edit.html,sha256=6UQf_qbhFDys3cVsTp-c7ABWZpggW9R
361
363
  chellow/templates/g/supply_notes.html,sha256=WR3YwGh_qqTklSJ7JqWX6BKBc9rk_jMff4RiWZiF2CM,936
362
364
  chellow/templates/g/unit.html,sha256=KouNVU0-i84afANkLQ_heJ0uDfJ9H5A05PuLqb8iCN8,438
363
365
  chellow/templates/g/units.html,sha256=p5Nd-lAIboKPEOO6N451hx1bcKxMg4BDODnZ-43MmJc,441
364
- chellow-1712839751.0.0.dist-info/METADATA,sha256=EB_do6nJy1ej8iMZCeeoOtGTPd6-Rj69sMJYdLQQDrI,12203
365
- chellow-1712839751.0.0.dist-info/WHEEL,sha256=as-1oFTWSeWBgyzh0O_qF439xqBe6AbBgt4MfYe5zwY,87
366
- chellow-1712839751.0.0.dist-info/RECORD,,
366
+ chellow-1712929217.0.0.dist-info/METADATA,sha256=y7dYnKInV3Z2jmy6Z1aKrqsJ-hwDU4ncat88dfZKzmo,12205
367
+ chellow-1712929217.0.0.dist-info/WHEEL,sha256=as-1oFTWSeWBgyzh0O_qF439xqBe6AbBgt4MfYe5zwY,87
368
+ chellow-1712929217.0.0.dist-info/RECORD,,