chellow 1760028799.0.0__py3-none-any.whl → 1761297878.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/dloads.py CHANGED
@@ -91,7 +91,7 @@ class DloadFile:
91
91
  def open_file(base, user, mode="r", newline=None, is_zip=False):
92
92
  global download_id
93
93
 
94
- base = base.replace("/", "").replace(" ", "")
94
+ base = "".join(x if (x.isalnum() or x in "_.") else "_" for x in base)
95
95
  try:
96
96
  lock.acquire()
97
97
  if len(list(download_path.iterdir())) == 0:
@@ -108,7 +108,7 @@ def open_file(base, user, mode="r", newline=None, is_zip=False):
108
108
  un = user.proxy_username
109
109
  else:
110
110
  un = user.email_address
111
- uname = un.replace("@", "").replace(".", "").replace("\\", "")
111
+ uname = "".join(x if (x.isalnum() or x in "_") else "_" for x in un)
112
112
 
113
113
  names = tuple("_".join((serial, v, uname, base)) for v in ("RUNNING", "FINISHED"))
114
114
  running_name, finished_name = tuple(download_path / name for name in names)
chellow/e/views.py CHANGED
@@ -381,7 +381,7 @@ def channel_snags_get():
381
381
  total_snags_q = total_snags_q.where(Era.dc_contract == contract)
382
382
  snags_q = snags_q.where(Era.dc_contract == contract)
383
383
 
384
- total_snags = g.sess.execute(total_snags_q)
384
+ total_snags = g.sess.scalars(total_snags_q).one()
385
385
 
386
386
  snag_groups = []
387
387
  prev_snag = None
@@ -5711,7 +5711,7 @@ def supplier_contract_edit_delete(contract_id):
5711
5711
  contract = Contract.get_supplier_by_id(g.sess, contract_id)
5712
5712
  contract.delete(g.sess)
5713
5713
  g.sess.commit()
5714
- return chellow_redirect("/supplier_contracts", 303)
5714
+ return hx_redirect("/supplier_contracts", 303)
5715
5715
  except BadRequest as e:
5716
5716
  g.sess.rollback()
5717
5717
  description = e.description
@@ -5882,25 +5882,39 @@ def supplier_rate_script_edit_get(rate_script_id):
5882
5882
  )
5883
5883
 
5884
5884
 
5885
+ @e.route("/supplier_rate_scripts/<int:rate_script_id>/edit", methods=["DELETE"])
5886
+ def supplier_rate_script_edit_delete(rate_script_id):
5887
+ try:
5888
+ rate_script = RateScript.get_supplier_by_id(g.sess, rate_script_id)
5889
+ contract = rate_script.contract
5890
+ contract.delete_rate_script(g.sess, rate_script)
5891
+ g.sess.commit()
5892
+ return hx_redirect(f"/supplier_contracts/{contract.id}", 303)
5893
+ except BadRequest as e:
5894
+ g.sess.rollback()
5895
+ flash(e.description)
5896
+ return make_response(
5897
+ render_template(
5898
+ "supplier_rate_script_edit.html", supplier_rate_script=rate_script
5899
+ ),
5900
+ 400,
5901
+ )
5902
+
5903
+
5885
5904
  @e.route("/supplier_rate_scripts/<int:rate_script_id>/edit", methods=["POST"])
5886
5905
  def supplier_rate_script_edit_post(rate_script_id):
5887
5906
  try:
5888
5907
  rate_script = RateScript.get_supplier_by_id(g.sess, rate_script_id)
5889
5908
  contract = rate_script.contract
5890
- if "delete" in request.values:
5891
- contract.delete_rate_script(g.sess, rate_script)
5892
- g.sess.commit()
5893
- return chellow_redirect(f"/supplier_contracts/{contract.id}", 303)
5894
- else:
5895
- script = req_zish("script")
5896
- start_date = req_date("start")
5897
- has_finished = req_bool("has_finished")
5898
- finish_date = req_date("finish") if has_finished else None
5899
- contract.update_rate_script(
5900
- g.sess, rate_script, start_date, finish_date, script
5901
- )
5902
- g.sess.commit()
5903
- return chellow_redirect(f"/supplier_rate_scripts/{rate_script.id}", 303)
5909
+ script = req_zish("script")
5910
+ start_date = req_date("start")
5911
+ has_finished = req_bool("has_finished")
5912
+ finish_date = req_date("finish") if has_finished else None
5913
+ contract.update_rate_script(
5914
+ g.sess, rate_script, start_date, finish_date, script
5915
+ )
5916
+ g.sess.commit()
5917
+ return chellow_redirect(f"/supplier_rate_scripts/{rate_script.id}", 303)
5904
5918
  except BadRequest as e:
5905
5919
  g.sess.rollback()
5906
5920
  flash(e.description)
@@ -1116,10 +1116,7 @@ def do_post(sess):
1116
1116
  exp_mpan_core if imp_mpan_core is None else imp_mpan_core
1117
1117
  ]
1118
1118
 
1119
- if "compression" in request.values:
1120
- compression = req_bool("compression")
1121
- else:
1122
- compression = True
1119
+ compression = req_bool("compression")
1123
1120
 
1124
1121
  user = g.user
1125
1122
 
@@ -0,0 +1,294 @@
1
+ import csv
2
+ import sys
3
+ import threading
4
+ import traceback
5
+
6
+ from dateutil.relativedelta import relativedelta
7
+
8
+ from flask import g, redirect, render_template, request
9
+
10
+ from sqlalchemy.orm import joinedload
11
+ from sqlalchemy.sql.expression import false, select, true
12
+
13
+ from werkzeug.exceptions import BadRequest
14
+
15
+ from chellow.dloads import open_file
16
+ from chellow.models import (
17
+ Channel,
18
+ Contract,
19
+ Era,
20
+ Party,
21
+ RSession,
22
+ Site,
23
+ SiteEra,
24
+ Snag,
25
+ Supply,
26
+ User,
27
+ )
28
+ from chellow.utils import (
29
+ csv_make_val,
30
+ hh_before,
31
+ req_bool,
32
+ req_date,
33
+ req_int,
34
+ req_int_none,
35
+ req_str,
36
+ utc_datetime_now,
37
+ )
38
+
39
+
40
+ def _make_rows(
41
+ sess,
42
+ now,
43
+ contract,
44
+ days_hidden,
45
+ is_ignored,
46
+ show_settlement,
47
+ only_ongoing,
48
+ days_long_hidden,
49
+ limit=None,
50
+ ):
51
+ cutoff_date = now - relativedelta(days=days_hidden)
52
+ q = (
53
+ select(Snag, Channel, Era, Supply, SiteEra, Site, Contract)
54
+ .join(Channel, Snag.channel_id == Channel.id)
55
+ .join(Era, Channel.era_id == Era.id)
56
+ .join(Supply, Era.supply_id == Supply.id)
57
+ .join(SiteEra, Era.site_eras)
58
+ .join(Site, SiteEra.site_id == Site.id)
59
+ .join(Contract, Era.dc_contract_id == Contract.id)
60
+ .where(
61
+ SiteEra.is_physical == true(),
62
+ Snag.start_date < cutoff_date,
63
+ )
64
+ .order_by(
65
+ Site.code,
66
+ Supply.id,
67
+ Channel.imp_related,
68
+ Channel.channel_type,
69
+ Snag.description,
70
+ Snag.start_date,
71
+ Snag.id,
72
+ )
73
+ .options(joinedload(Era.dc_contract))
74
+ )
75
+ if contract is not None:
76
+ q = q.where(Era.dc_contract == contract)
77
+
78
+ if not is_ignored:
79
+ q = q.where(Snag.is_ignored == false())
80
+
81
+ if show_settlement == "yes":
82
+ q = q.join(Party, Supply.dno_id == Party.id).where(Party.dno_code != "99")
83
+ elif show_settlement == "no":
84
+ q = q.join(Party, Supply.dno_id == Party.id).where(Party.dno_code == "99")
85
+ elif show_settlement == "both":
86
+ pass
87
+ else:
88
+ raise BadRequest("show_settlement must be 'yes', 'no' or 'both'.")
89
+
90
+ snag_groups = []
91
+ prev_snag = None
92
+ for snag, channel, era, supply, site_era, site, contract in sess.execute(q):
93
+ snag_start = snag.start_date
94
+ snag_finish = snag.finish_date
95
+
96
+ if snag_finish is None:
97
+ duration = now - snag_start
98
+ age_of_snag = None
99
+ else:
100
+ duration = snag_finish - snag_start
101
+ if hh_before(cutoff_date, snag_finish):
102
+ age_of_snag = None
103
+ else:
104
+ delta = now - snag_finish
105
+ age_of_snag = delta.days
106
+
107
+ if only_ongoing and age_of_snag is not None:
108
+ continue
109
+
110
+ if days_long_hidden is not None and duration < days_long_hidden:
111
+ continue
112
+
113
+ if (
114
+ prev_snag is None
115
+ or channel.era != prev_snag.channel.era
116
+ or snag.start_date != prev_snag.start_date
117
+ or snag.finish_date != prev_snag.finish_date
118
+ or snag.description != prev_snag.description
119
+ ):
120
+ if limit is not None and len(snag_groups) > limit:
121
+ break
122
+
123
+ snag_group = {
124
+ "snags": [],
125
+ "site": site,
126
+ "era": era,
127
+ "supply": supply,
128
+ "description": snag.description,
129
+ "start_date": snag.start_date,
130
+ "finish_date": snag.finish_date,
131
+ "contract": contract,
132
+ "contract_name": contract.name,
133
+ "hidden_days": days_hidden,
134
+ "chellow_id": snag.id,
135
+ "imp_mpan_core": era.imp_mpan_core,
136
+ "exp_mpan_core": era.exp_mpan_core,
137
+ "site_code": site.code,
138
+ "site_name": site.name,
139
+ "snag_description": snag.description,
140
+ "channel_type": channel.channel_type,
141
+ "is_ignored": snag.is_ignored,
142
+ "days_since_finished": age_of_snag,
143
+ "duration": duration.days,
144
+ }
145
+ snag_groups.append(snag_group)
146
+ snag_group["snags"].append(snag)
147
+ prev_snag = snag
148
+
149
+ def make_key(item):
150
+ return item["duration"]
151
+
152
+ snag_groups.sort(key=make_key, reverse=True)
153
+ return snag_groups
154
+
155
+
156
+ def content(
157
+ contract_id,
158
+ days_hidden,
159
+ is_ignored,
160
+ user_id,
161
+ only_ongoing,
162
+ show_settlement,
163
+ days_long_hidden,
164
+ now,
165
+ ):
166
+ f = writer = None
167
+ try:
168
+ with RSession() as sess:
169
+ user = User.get_by_id(sess, user_id)
170
+ if contract_id is None:
171
+ contract = None
172
+ namef = "all"
173
+ else:
174
+ contract = Contract.get_dc_by_id(sess, contract_id)
175
+ namef = contract.name
176
+
177
+ f = open_file(f"channel_snags_{namef}.csv", user, mode="w", newline="")
178
+ writer = csv.writer(f, lineterminator="\n")
179
+ titles = (
180
+ "contract",
181
+ "hidden_days",
182
+ "chellow_ids",
183
+ "imp_mpan_core",
184
+ "exp_mpan_core",
185
+ "site_code",
186
+ "site_name",
187
+ "snag_description",
188
+ "channel_types",
189
+ "start_date",
190
+ "finish_date",
191
+ "is_ignored",
192
+ "days_since_finished",
193
+ "duration",
194
+ )
195
+ writer.writerow(titles)
196
+
197
+ for snag_group in _make_rows(
198
+ sess,
199
+ now,
200
+ contract,
201
+ days_hidden,
202
+ is_ignored,
203
+ show_settlement,
204
+ only_ongoing,
205
+ days_long_hidden,
206
+ ):
207
+
208
+ vals = {
209
+ "contract": snag_group["contract"].name,
210
+ "hidden_days": days_hidden,
211
+ "chellow_ids": [snag.id for snag in snag_group["snags"]],
212
+ "imp_mpan_core": snag_group["imp_mpan_core"],
213
+ "exp_mpan_core": snag_group["exp_mpan_core"],
214
+ "site": snag_group["site"],
215
+ "site_code": snag_group["site"].code,
216
+ "site_name": snag_group["site"].name,
217
+ "snag_description": snag_group["description"],
218
+ "channel_types": [
219
+ f"{s.channel.imp_related}_{s.channel.channel_type}"
220
+ for s in snag_group["snags"]
221
+ ],
222
+ "start_date": snag_group["start_date"],
223
+ "finish_date": snag_group["finish_date"],
224
+ "is_ignored": snag_group["is_ignored"],
225
+ "days_since_finished": snag_group["days_since_finished"],
226
+ "duration": snag_group["duration"],
227
+ }
228
+
229
+ writer.writerow(csv_make_val(vals[t]) for t in titles)
230
+ except BaseException:
231
+ msg = traceback.format_exc()
232
+ sys.stderr.write(msg)
233
+ writer.writerow([msg])
234
+ finally:
235
+ if f is not None:
236
+ f.close()
237
+
238
+
239
+ LIMIT = 200
240
+
241
+
242
+ def do_get(sess):
243
+ contract_id = req_int_none("dc_contract_id")
244
+ days_hidden = req_int("days_hidden")
245
+ is_ignored = req_bool("is_ignored")
246
+ only_ongoing = req_bool("only_ongoing")
247
+ show_settlement = req_str("show_settlement")
248
+ as_csv = req_bool("as_csv")
249
+ days_long_hidden = req_int_none("days_long_hidden")
250
+ if "now_year" in request.values:
251
+ now = req_date("now")
252
+ else:
253
+ now = utc_datetime_now()
254
+
255
+ if as_csv:
256
+ args = (
257
+ contract_id,
258
+ days_hidden,
259
+ is_ignored,
260
+ g.user.id,
261
+ only_ongoing,
262
+ show_settlement,
263
+ now,
264
+ )
265
+ threading.Thread(target=content, args=args).start()
266
+ return redirect("/downloads", 303)
267
+ else:
268
+ if contract_id is None:
269
+ contract = None
270
+ else:
271
+ contract = Contract.get_dc_by_id(sess, contract_id)
272
+ now = utc_datetime_now()
273
+ snag_groups = _make_rows(
274
+ sess,
275
+ now,
276
+ contract,
277
+ days_hidden,
278
+ is_ignored,
279
+ show_settlement,
280
+ only_ongoing,
281
+ days_long_hidden,
282
+ limit=LIMIT,
283
+ )
284
+ return render_template(
285
+ "reports/channel_snags.html",
286
+ contract=contract,
287
+ limit=LIMIT,
288
+ snag_groups=snag_groups,
289
+ days_hidden=days_hidden,
290
+ is_ignored=is_ignored,
291
+ only_ongoing=only_ongoing,
292
+ show_settlement=show_settlement,
293
+ days_long_hidden=days_long_hidden,
294
+ )
@@ -8,6 +8,15 @@
8
8
  Channel Snags
9
9
  {% endblock %}
10
10
 
11
+ {% block inside_head %}
12
+ <style>
13
+ #snags.htmx-swapping {
14
+ opacity: 0;
15
+ transition: opacity 1s ease-out;
16
+ }
17
+ </style>
18
+ {% endblock %}
19
+
11
20
  {% block nav %}
12
21
  <a href="/e/dc_contracts">DC Contracts</a> &raquo;
13
22
  {% if contract %}
@@ -17,113 +26,36 @@
17
26
  {% endblock %}
18
27
 
19
28
  {% block content %}
20
-
21
- <form action="/reports/233">
29
+ <form hx-get="/reports/channel_snags" hx-params="*" hx-include="this" hx-trigger="load, change"
30
+ hx-target="#snags" hx-swap="outerHTML swap:1s">
22
31
  <fieldset>
23
- <legend>Download CSV</legend>
32
+ <legend>Filters</legend>
24
33
  <input type="hidden" name="dc_contract_id" value="{{contract.id}}">
25
34
  <label>Hide snags &lt; days old</label>
26
- {{input_text('days_hidden', '0', 3, 3)}}
35
+ {{input_text('days_hidden', initial='5', size=3, maxlength=3)}}
27
36
  <label>Include ignored snags</label> {{input_checkbox('is_ignored', False)}}
28
- <input type="submit" value="Download">
29
- </fieldset>
30
- </form>
31
37
 
32
- <p>
33
- The 'Days Since Snag Finished' column is blank if the snag is ongoing (ie. if the
34
- finish date is 'ongoing' or if it finished less than 'Days Hidden' ago).
35
- </p>
38
+ <label>Only show ongoing snags</label>
39
+ {{input_checkbox('only_ongoing', initial=True)}}
36
40
 
37
- <form action="/e/channel_snags">
38
- <fieldset>
39
- <legend>Show Channel Snags</legend>
40
- <input type="hidden" name="dc_contract_id" value="
41
- {%- if contract -%}
42
- {{contract.id}}
43
- {%- endif -%}
44
- ">
45
- <label>Hide snags &lt; days old</label>
46
- {{input_text('days_hidden', '0', 3, 3)}}
47
- <label>Include ignored snags</label> {{input_checkbox('is_ignored', False)}}
48
- <input type="submit" value="Show">
49
- </fieldset>
50
- </form>
41
+ <fieldset>
42
+
43
+ <label>Show settlement only</label>
44
+ {{input_radio('show_settlement', 'yes', initial=False)}}
45
+
46
+ <label>Show non-settlement only</label>
47
+ {{input_radio('show_settlement', 'no', initial=False)}}
51
48
 
52
- <p>
53
- There are {{total_snags}} snag(s) older than {{request.values.days_hidden}} days
54
- {%- if not is_ignored %}
55
- that aren't ignored
56
- {%- endif -%}.
57
- </p>
49
+ <label>Show both</label>
50
+ {{input_radio('show_settlement', 'both', initial=True)}}
58
51
 
59
- <table>
60
- <caption>Snags (truncated after 200)</caption>
61
- <thead>
62
- <tr>
63
- <th>View</th>
64
- <th>Contract</th>
65
- <th>Import MPAN Core</th>
66
- <th>Export MPAN Core</th>
67
- <th>Sites</th>
68
- <th>Snag Description</th>
69
- <th>Channels</th>
70
- <th>Duration</th>
71
- </tr>
72
- </thead>
73
- <tbody>
74
- {% for snag_group in snag_groups %}
75
- <tr>
76
- <td>
77
- <ul>
78
- {% for snag in snag_group.snags %}
79
- <li>
80
- <a href="/e/channel_snags/{{snag.id}}">view</a>
81
- [<a href="/e/channel_snags/{{snag.id}}/edit">edit</a>]
82
- {% if snag.is_ignored %} ignored{% endif %}
83
- </li>
84
- {% endfor %}
85
- </ul>
86
- </td>
87
- <td>{{snag_group.era.dc_contract.name}}</td>
88
- <td>
89
- {% if snag_group.era.imp_mpan_core %}
90
- {{snag_group.era.imp_mpan_core}}
91
- {% endif %}
92
- </td>
93
- <td>
94
- {% if snag_group.era.exp_mpan_core %}
95
- {{snag_group.era.exp_mpan_core}}
96
- {% endif %}
97
- </td>
98
- <td>
99
- <ul>
100
- {% for site in snag_group.sites %}
101
- <li>{{site.code}} {{site.name}}</li>
102
- {% endfor %}
103
- </ul>
104
- </td>
105
- <td>{{snag_group.description}}</td>
106
- <td>
107
- <ul>
108
- {% for snag in snag_group.snags %}
109
- <li>
110
- {% if snag.channel.imp_related %}
111
- Import
112
- {% else %}
113
- Export
114
- {% endif %}
115
- {{snag.channel.channel_type}}
116
- </li>
117
- {% endfor %}
118
- </ul>
119
- </td>
120
- <td>
121
- {{snag_group.start_date|hh_format}} to
122
- {{snag_group.finish_date|hh_format}}
123
- </td>
124
- </tr>
125
- {% endfor %}
126
- </tbody>
127
- </table>
52
+ </fieldset>
53
+
54
+ <label>Hide snags &lt; days long</label>
55
+ {{input_text('days_long_hidden', initial='', size=3, maxlength=3)}}
56
+
57
+ </fieldset>
58
+ </form>
128
59
 
60
+ <div id="snags"></div>
129
61
  {% endblock %}
@@ -12,41 +12,29 @@
12
12
 
13
13
  {% block content %}
14
14
 
15
- {% if request.method == 'GET' and request.values.delete %}
16
- <form method="post">
17
- <fieldset>
18
- <legend>Are you sure you want to delete this contract?</legend>
19
- <input type="submit" name="delete" value="Delete">
20
- <a href="/e/supplier_contracts/{{contract.id}}/edit">Cancel</a>
21
- </fieldset>
22
- </form>
23
-
24
- {% else %}
25
-
26
- <form method="post">
27
- <fieldset>
28
- <legend>Update Contract</legend>
29
- <label>Party</label>
30
- <select name="party_id">
31
- {% for party in parties %}
32
- {{ input_option('party_id', party.id, party.participant.code + ' : ' + party.name, contract.party.id) }}
33
- {% endfor %}
34
- </select>
35
- <label>Name</label>
36
- {{input_text('name', contract.name)}}
37
- <label>Charge script</label>
38
- {{input_textarea('charge_script', contract.charge_script, 40, 80)}}
39
- <label>Properties</label>
40
- {{input_textarea('properties', contract.properties, 20, 80)}}
41
- <input type="submit" value="Update">
42
- </fieldset>
43
- </form>
15
+ <form method="post">
16
+ <fieldset>
17
+ <legend>Update Contract</legend>
18
+ <label>Party</label>
19
+ <select name="party_id">
20
+ {% for party in parties %}
21
+ {{ input_option('party_id', party.id, party.participant.code + ' : ' + party.name, contract.party.id) }}
22
+ {% endfor %}
23
+ </select>
24
+ <label>Name</label>
25
+ {{input_text('name', contract.name)}}
26
+ <label>Charge script</label>
27
+ {{input_textarea('charge_script', contract.charge_script, 40, 80, show_pos=True)}}
28
+ <label>Properties</label>
29
+ {{input_textarea('properties', contract.properties, 20, 80)}}
30
+ <input type="submit" value="Update">
31
+ </fieldset>
32
+ </form>
44
33
 
45
- <form>
46
- <fieldset>
47
- <legend>Delete this contract</legend>
48
- <input type="submit" name="delete" value="Delete">
49
- </fieldset>
50
- </form>
51
- {% endif %}
34
+ <form hx-delete="/e/supplier_contracts/{{contract.id}}/edit" hx-confirm="Are you sure you want to delete this contract?">
35
+ <fieldset>
36
+ <legend>Delete this contract</legend>
37
+ <input type="submit" name="delete" value="Delete">
38
+ </fieldset>
39
+ </form>
52
40
  {% endblock %}
@@ -14,52 +14,37 @@
14
14
  {% endblock %}
15
15
 
16
16
  {% block content %}
17
- {% if request.method == 'GET' and request.values.delete %}
18
-
19
- <form method="post"
20
- action="/e/supplier_rate_scripts/{{supplier_rate_script.id}}/edit">
21
- <fieldset>
22
- <legend>Are you sure you want to delete this rate script?</legend>
23
- <input type="submit" name="delete" value="Delete">
24
- <a href="/supplier_rate_scripts/{{supplier_rate_script.id}}/edit">Cancel</a>
25
- </fieldset>
26
- </form>
27
-
28
- {% else %}
29
-
30
- <form action="" method="post">
17
+ <form action="/e/supplier_rate_scripts/{{supplier_rate_script.id}}/edit" method="post">
18
+ <fieldset>
19
+ <legend>Update Rate Script</legend>
31
20
  <fieldset>
32
- <legend>Update Rate Script</legend>
33
- <fieldset>
34
- <legend>Start date</legend>
35
- {{ input_date('start', supplier_rate_script.start_date) }}
36
- </fieldset>
37
- <fieldset>
38
- <legend>Finish date</legend>
39
- <label>
40
- Ended?
41
- {{input_checkbox('has_finished', supplier_rate_script.finish_date != None)}}
42
- </label>
43
- {{ input_date('finish', supplier_rate_script.finish_date) }}
44
- </fieldset>
45
- <label>Script</label>
46
- {{ input_textarea(
47
- 'script', supplier_rate_script.script, 40, 80, show_pos=True) }}
48
- <input type="submit" value="Update">
21
+ <legend>Start date</legend>
22
+ {{ input_date('start', supplier_rate_script.start_date) }}
49
23
  </fieldset>
50
- </form>
51
-
52
- {% if rate_script_example %}
53
- <h4>Example</h4>
54
- <pre>{{rate_script_example}}</pre>
55
- {% endif %}
56
-
57
- <form>
58
24
  <fieldset>
59
- <legend>Delete this Rate Script</legend>
60
- <input type="submit" name="delete" value="Delete">
25
+ <legend>Finish date</legend>
26
+ <label>
27
+ Ended?
28
+ {{input_checkbox('has_finished', supplier_rate_script.finish_date != None)}}
29
+ </label>
30
+ {{ input_date('finish', supplier_rate_script.finish_date) }}
61
31
  </fieldset>
62
- </form>
63
-
32
+ <label>Script</label>
33
+ {{ input_textarea(
34
+ 'script', supplier_rate_script.script, 40, 80, show_pos=True) }}
35
+ <input type="submit" value="Update">
36
+ </fieldset>
37
+ </form>
38
+
39
+ {% if rate_script_example %}
40
+ <h4>Example</h4>
41
+ <pre>{{rate_script_example}}</pre>
64
42
  {% endif %}
43
+
44
+ <form hx-delete="/e/supplier_rate_scripts/{{supplier_rate_script.id}}/edit" hx-confirm="Are you sure you want to delete this rate script?">
45
+ <fieldset>
46
+ <legend>Delete this Rate Script</legend>
47
+ <input type="submit" name="delete" value="Delete">
48
+ </fieldset>
49
+ </form>
65
50
  {% endblock %}
@@ -124,13 +124,23 @@
124
124
  </div>
125
125
  {%- endmacro -%}
126
126
 
127
- {%- macro input_checkbox(name, initial) %}
128
- <input type="checkbox" name="{{ name }}" value="true"
127
+ {%- macro input_checkbox(name, initial=False, value='true') %}
128
+ <input type="checkbox" name="{{ name }}" value="{{value}}"
129
129
  {%- if request.values[name] -%}
130
- {%- if request.values[name] == 'true' %} checked
130
+ {%- if request.values[name] == value %} checked
131
131
  {%- endif -%}
132
132
  {%- else -%}
133
- {%- if initial == True %} checked{% endif -%}
133
+ {%- if initial %} checked{% endif -%}
134
+ {%- endif -%}>
135
+ {%- endmacro -%}
136
+
137
+ {%- macro input_radio(name, value, initial=False) %}
138
+ <input type="radio" name="{{ name }}" value="{{value}}"
139
+ {%- if request.values[name] -%}
140
+ {%- if request.values[name] == value %} checked
141
+ {%- endif -%}
142
+ {%- else -%}
143
+ {%- if initial %} checked{% endif -%}
134
144
  {%- endif -%}>
135
145
  {%- endmacro -%}
136
146
 
@@ -0,0 +1,91 @@
1
+ {% extends "macros.html" %}
2
+
3
+ {% block html %}
4
+ <table class="sticky" id="snags">
5
+ <caption>
6
+ Snags (truncated after 200)
7
+ <a href="/reports/channel_snags?dc_contract_id={{contract.id}}&amp;days_hidden={{days_hidden}}&amp;is_ignored={{is_ignored}}&amp;only_ongoing={{only_ongoing}}&amp;show_settlement={{show_settlement}}&amp;as_csv=true">Download as CSV</a>
8
+ </caption>
9
+ <thead>
10
+ <tr>
11
+ <th>View</th>
12
+ <th>Contract</th>
13
+ <th>Import MPAN Core</th>
14
+ <th>Export MPAN Core</th>
15
+ <th>Site</th>
16
+ <th>Description</th>
17
+ <th>Channels</th>
18
+ <th>Start Date</th>
19
+ <th>Finish Date</th>
20
+ <th>
21
+ <span title="Blank if the snag is ongoing (ie. if the finish date is 'ongoing' or if it finished less than 'Days Hidden' ago).">Days Since Finished</span>
22
+ </th>
23
+ <th>Duration</th>
24
+ <th>Comm</th>
25
+ </tr>
26
+ </thead>
27
+ <tbody>
28
+ {% for snag_group in snag_groups %}
29
+ <tr>
30
+ <td>
31
+ <ul>
32
+ {% for snag in snag_group.snags %}
33
+ <li>
34
+ <a href="/e/channel_snags/{{snag.id}}">view</a>
35
+ [<a href="/e/channel_snags/{{snag.id}}/edit">edit</a>]
36
+ {% if snag.is_ignored %} ignored{% endif %}
37
+ </li>
38
+ {% endfor %}
39
+ </ul>
40
+ </td>
41
+ <td>
42
+ <a href="/e/dc_contracts/{{snag_group.contract.id}}">{{snag_group.contract.name}}</a>
43
+ </td>
44
+ <td>
45
+ {% if snag_group.era.imp_mpan_core %}
46
+ <a href="/e/supplies/{{snag_group.supply.id}}">{{snag_group.era.imp_mpan_core}}</a>
47
+ {% endif %}
48
+ </td>
49
+ <td>
50
+ {% if snag_group.era.exp_mpan_core %}
51
+ <a href="/e/supplies/{{snag_group.supply.id}}">{{snag_group.era.exp_mpan_core}}</a>
52
+ {% endif %}
53
+ </td>
54
+ <td>
55
+ <a href="/sites/{{snag_group.site.id}}">{{snag_group.site.code}} {{snag_group.site.name}}</a>
56
+ </td>
57
+ <td>{{snag_group.description}}</td>
58
+ <td>
59
+ <ul>
60
+ {% for snag in snag_group.snags %}
61
+ <li>
62
+ {% if snag.channel.imp_related %}
63
+ Import
64
+ {% else %}
65
+ Export
66
+ {% endif %}
67
+ {{snag.channel.channel_type}}
68
+ </li>
69
+ {% endfor %}
70
+ </ul>
71
+ </td>
72
+ <td>
73
+ {{snag_group.start_date|hh_format}}
74
+ </td>
75
+ <td>
76
+ {{snag_group.finish_date|hh_format}}
77
+ </td>
78
+ <td>
79
+ {% if snag_group.days_since_finished %}
80
+ {{snag_group.days_since_finished}}
81
+ {% endif %}
82
+ </td>
83
+ <td>{{snag_group.duration}}</td>
84
+ <td>
85
+ <a href="/e/comms/{{snag_group.era.comm.id}}" title="{{snag_group.era.comm.description}}">{{snag_group.era.comm.code}}</a>
86
+ </td>
87
+ </tr>
88
+ {% endfor %}
89
+ </tbody>
90
+ </table>
91
+ {% endblock %}
chellow/utils.py CHANGED
@@ -29,11 +29,15 @@ def req_str(name):
29
29
  raise BadRequest(f"The field {name} is required.")
30
30
 
31
31
 
32
- def req_bool(name):
32
+ def req_strs(name):
33
33
  try:
34
- return request.values[name] == "true"
34
+ return request.values.getlist(name)
35
35
  except KeyError:
36
- return False
36
+ raise BadRequest(f"The field {name} is required.")
37
+
38
+
39
+ def req_bool(name):
40
+ return name in request.values
37
41
 
38
42
 
39
43
  def req_int(name):
@@ -43,6 +47,13 @@ def req_int(name):
43
47
  raise BadRequest(f"Problem parsing the field {name} as an integer: {e}")
44
48
 
45
49
 
50
+ def req_ints(name):
51
+ try:
52
+ return [int(v) for v in req_strs(name)]
53
+ except ValueError as e:
54
+ raise BadRequest(f"Problem parsing the field {name} as an integer: {e}")
55
+
56
+
46
57
  def req_int_none(name):
47
58
  val = req_str(name)
48
59
  if val == "":
@@ -1,30 +1,28 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chellow
3
- Version: 1760028799.0.0
3
+ Version: 1761297878.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)
7
7
  Classifier: Operating System :: OS Independent
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Requires-Python: >=3.11
10
- Requires-Dist: flask-restx==1.3.0
11
- Requires-Dist: flask==3.1.1
12
- Requires-Dist: jsonschema==4.17.3
10
+ Requires-Dist: flask-restx==1.3.2
11
+ Requires-Dist: flask==3.1.2
13
12
  Requires-Dist: odio==0.0.23
14
13
  Requires-Dist: openpyxl==3.1.5
15
- Requires-Dist: paramiko==3.4.1
16
- Requires-Dist: pep3143daemon==0.1.0
14
+ Requires-Dist: paramiko==4.0.0
17
15
  Requires-Dist: pg8000==1.31.5
18
16
  Requires-Dist: pip>=9.0.1
19
- Requires-Dist: psutil==5.9.5
20
- Requires-Dist: pympler==1.0.1
21
- Requires-Dist: pypdf==6.0.0
17
+ Requires-Dist: psutil==7.1.1
18
+ Requires-Dist: pympler==1.1
19
+ Requires-Dist: pypdf==6.1.3
22
20
  Requires-Dist: python-dateutil==2.8.2
23
- Requires-Dist: pytz==2022.6
24
- Requires-Dist: requests==2.32.4
25
- Requires-Dist: sqlalchemy==2.0.41
26
- Requires-Dist: waitress==3.0.1
27
- Requires-Dist: xlrd==2.0.1
21
+ Requires-Dist: pytz==2025.2
22
+ Requires-Dist: requests==2.32.5
23
+ Requires-Dist: sqlalchemy==2.0.44
24
+ Requires-Dist: waitress==3.0.2
25
+ Requires-Dist: xlrd==2.0.2
28
26
  Requires-Dist: zish==0.1.12
29
27
  Description-Content-Type: text/markdown
30
28
 
@@ -1,8 +1,7 @@
1
1
  chellow/__init__.py,sha256=9aoSbGmHCIHwzI_c_TLSg5CeKwqAq0I6kKaMvTrTMqg,10936
2
2
  chellow/api.py,sha256=mk17TfweR76DPFC8lX2SArTjai6y6YshASxqO1w-_-s,11036
3
3
  chellow/bank_holidays.py,sha256=T_utYMwe_g1dz5X-aOTdIPryg49SvB7QsWM1yphlqG8,4423
4
- chellow/commands.py,sha256=ESBe9ZWj1c3vdZgqMZ9gFvYAB3hRag2R1PzOwuw9yFo,1302
5
- chellow/dloads.py,sha256=dixp-O0MF2_mlwrnKx3D9DH09Qu05BjTo0rZfigTjR4,5534
4
+ chellow/dloads.py,sha256=sNAPMe4LeHqfisEubGXvraDsUS0F-ujI3WG0Md8DywM,5565
6
5
  chellow/edi_lib.py,sha256=Lq70TUJuogoP5KGrphzUEUfyfgftEclg_iA3mpNAaDI,51324
7
6
  chellow/fake_batch_updater.py,sha256=khAmvSUn9qN04w8C92kRg1UeyQvfLztE7QXv9tUz6nE,11611
8
7
  chellow/general_import.py,sha256=ghybbden66VT4q5J0vYwiNg-6G2vg71EgCN_x3fvhW0,69200
@@ -12,7 +11,7 @@ chellow/proxy.py,sha256=cVXIktPlX3tQ1BYcwxq0nJXKE6r3DtFTtfFHPq55HaM,1351
12
11
  chellow/rate_server.py,sha256=RwJo-AzBIdzxx7PAtboZEUH1nUjAeJckw0bk06-9oyM,5672
13
12
  chellow/rrun.py,sha256=sWm_tuJ_6xH3T28TY1w63k1Q44N_S_p_pYF5YCeztqU,2226
14
13
  chellow/testing.py,sha256=Dj2c1NX8lVlygueOrh2eyYawLW6qKEHxNhXVVUaNRO0,3637
15
- chellow/utils.py,sha256=i3GQK9MIcweosZk2gi-nX_IFq2DxURAJDyNoLBg6YwM,19421
14
+ chellow/utils.py,sha256=x6yReQkhFaFGnsuhs6PqrEkE1OfZSPrrC7IqstRXKKU,19701
16
15
  chellow/views.py,sha256=9IUUbYjqEmQQzbYQB4w-ygCo25gecIxYVbxTXRF-YfY,86078
17
16
  chellow/e/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
17
  chellow/e/aahedc.py,sha256=d2usudp7KYWpU6Pk3fal5EQ47EbvkvKeaFGylnb3NWw,606
@@ -45,7 +44,7 @@ chellow/e/system_price.py,sha256=6w5J7bzwFAZubE2zdOFRiS8IIrVP8hkoIOaG2yCt-Ic,623
45
44
  chellow/e/tlms.py,sha256=pyL32hPiYd09FbpXVMnBjHsWFEQHs-Az945V7EShGiY,9116
46
45
  chellow/e/tnuos.py,sha256=NBmc-f3oezrl4gviAKobljHfICTpBKxxxEGBGJi_lRk,4927
47
46
  chellow/e/triad.py,sha256=uQIngSrz8irBXQ0Rp_s8nAUzu-y2Ms7aj4B38_Ff8y8,13720
48
- chellow/e/views.py,sha256=UH9Wc6UOiCcs9OtXWU8MK2eBi9d_J2xJN6qmHgZVat0,234245
47
+ chellow/e/views.py,sha256=Duvdp5ZjtCHnOrAyk7uZZJr0jZcg0F2h3g1XNN25HtU,234669
49
48
  chellow/e/bill_parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
49
  chellow/e/bill_parsers/activity_mop_stark_xlsx.py,sha256=hcbjxqLOe7qkDjS7enCpmfyGwm3d-pq3u5VQIaUmVTw,3796
51
50
  chellow/e/bill_parsers/annual_mop_stark_xlsx.py,sha256=yoEGQS0qcrv3TWFfxELIIi8f1CyKcIzh0xVaPoz2w2s,4191
@@ -86,9 +85,8 @@ chellow/reports/report_183.py,sha256=fGONpKEIXTqIT_3dE5jZKBIesWPgLq-chHO6X22dIv0
86
85
  chellow/reports/report_187.py,sha256=RCvxDnkfRu7ocKUPEX0qtVphGOBEl9RULy790uteXcQ,9915
87
86
  chellow/reports/report_219.py,sha256=3Zs16ka6dWWY_lCDoA-ZFrlPb4vHk1LQoa2yrlTMntA,7117
88
87
  chellow/reports/report_231.py,sha256=WARLztV2HzBFSi5_xvY_DTXVyuBBbBYiU0RazfjKw-Y,5270
89
- chellow/reports/report_233.py,sha256=qJdPFuBgQAWD24BsljgGJ44L_RGdHOA1RqsxGjTWhFc,4975
90
88
  chellow/reports/report_241.py,sha256=lfivElRkV_CZAhck0rxAuhqzgponj3aWwmwGMNQvUxg,5343
91
- chellow/reports/report_247.py,sha256=KuNCWfYt1WByFv3EfiJF1-5blhJVWLbAdhlI5KD9HxQ,43346
89
+ chellow/reports/report_247.py,sha256=pbWfM-fUeuakZxH1Ovlx7-XfUg6SL9t51swK3yMc1lc,43253
92
90
  chellow/reports/report_29.py,sha256=ZXnq6Kg5on-IPquUsH4bn46mBVESY_WhwqDWZPXOJwo,2695
93
91
  chellow/reports/report_291.py,sha256=BnWtxe0eWN2QKKWpwjs5-RI5LbReBKL119QbkrkNhV8,7478
94
92
  chellow/reports/report_33.py,sha256=lt1EN_LNx6u-AgdaS3YRkPMZA33JgMcELolHF4oJUMw,16689
@@ -101,6 +99,7 @@ chellow/reports/report_87.py,sha256=udzbCuXcckWD-OHmfJCT6bwg_paYhm4vfDWlL8WM-jA,
101
99
  chellow/reports/report_asset_comparison.py,sha256=Cl2NgvbclqhOVvKuUu3sajTVO2JupMfzK3bV0_K8eNs,6077
102
100
  chellow/reports/report_batches.py,sha256=MoCv2dE-JgaJzaMjMA-kZrPkYR13uDoXer5UuF12OYc,4757
103
101
  chellow/reports/report_bills.py,sha256=LP7XDxzO0Fp10c8xDE67e4tHTEc7nN74ESQBy762Nx4,3401
102
+ chellow/reports/report_channel_snags.py,sha256=qBVmw0q588izKJr6dDOn7jjdFvSa-Ldd2uqtVETqijY,8748
104
103
  chellow/reports/report_csv_llfcs.py,sha256=mMB06b6Jems5kcQU4H4Le_fyKgVr8THP8BCx3pkvg5E,1903
105
104
  chellow/reports/report_csv_site_hh_data.py,sha256=ik0OkGVo1bfTXLdcT3gPUHnxSkSdorzZheP3qgnOR5c,4331
106
105
  chellow/reports/report_csv_site_snags.py,sha256=AuAy6vjL0g7vwPPAZhBqxOyITL9_jnyFj012MnUcxxk,2627
@@ -134,7 +133,7 @@ chellow/templates/home.html,sha256=7xiD6QLju3ugALzMlzTJosRyMUmQGWPK6jDF7oZbnAQ,5
134
133
  chellow/templates/input_date.html,sha256=rpgB5n0LfN8Y5djN_ZiuSxqdskxzCoKrEqI7hyJkVQo,1248
135
134
  chellow/templates/local_report.html,sha256=pV7_0QwyQ-D3OS9LXrly5pq3qprZnwTCoq6vCnMTkS4,1332
136
135
  chellow/templates/local_reports.html,sha256=4wbfVkY4wUfSSfWjxqIsvCpIsa9k7H_dGAjznrG5jNM,701
137
- chellow/templates/macros.html,sha256=zd5P3irbRMXiK6LnWdbv5M0SYX2roZKhEzNTt2_uPyM,5031
136
+ chellow/templates/macros.html,sha256=FlRTl9W2avRXErnS3vzeOxciGXJYoKptiR08iVmSpfw,5353
138
137
  chellow/templates/national_grid.html,sha256=8W92tsjlqPSB0J--nyFIi-wzFae9CyDr6fODXLZp8ic,991
139
138
  chellow/templates/non_core_auto_importer.html,sha256=s9SluRN1bHTHjd8GP6uFhk6LrZYG8dqBG3y94zUKe5Q,944
140
139
  chellow/templates/non_core_contract.html,sha256=BdGtxErTvw4DwXLb6B2vimuU6ode3fFA-90kBmHCwYc,1754
@@ -181,7 +180,7 @@ chellow/templates/e/channel_add.html,sha256=szwQJBHx2kAoSFomXDHD0N_7PSd4drqOoAWh
181
180
  chellow/templates/e/channel_edit.html,sha256=OUkdiS2NBQ_w4gmxRxXAcRToM5BT8DWWJrtQMFs-GUU,2198
182
181
  chellow/templates/e/channel_snag.html,sha256=wBJ5KBfeJdAeRmaB0qu0AD9Z4nM5fn6tJ_8bNqTWtoo,1477
183
182
  chellow/templates/e/channel_snag_edit.html,sha256=sUFI4Ml3F1B35x8_t_Pz3hWuQN9Xtxr3kt4u8hdxNwY,1911
184
- chellow/templates/e/channel_snags.html,sha256=PuYnDycsQcwmmW9TfCCa_WgJRHOFn9HAnNm51rH7l9k,3157
183
+ chellow/templates/e/channel_snags.html,sha256=TgRhS6DcOMyoAowwrCjPtq9iuX6lpvG3uchcd3g80kk,1593
185
184
  chellow/templates/e/comm.html,sha256=DSlAaDg1r4KYq9VUgDtt2lgW6apZjZvwhMujIJINmps,383
186
185
  chellow/templates/e/comms.html,sha256=bUXZePnMfpKk1E71qLGOSkx8r3GxdPPD_-MosIXXq4s,434
187
186
  chellow/templates/e/cop.html,sha256=ULv7ALFJHMUgPX96hQNm2irc3edtKYHS6fYAxvmzj8M,376
@@ -336,14 +335,14 @@ chellow/templates/e/supplier_bill_import.html,sha256=RXvRkKoH-s1F_Wz---WygiNCmAm
336
335
  chellow/templates/e/supplier_bill_import_contract.html,sha256=HySas6Q-1Pi6aZaEZNhKeK4c3d8MFbBIMh0PJ4zvKWQ,3117
337
336
  chellow/templates/e/supplier_contract.html,sha256=jz3_2q5HuJaxQhJPaM_dcukigYyI6UmhVYC6nPHY7Ng,3243
338
337
  chellow/templates/e/supplier_contract_add.html,sha256=gsozEtF24lzYi_Bb4LTenvh62tCt7dQ4CwaIz7rFck4,899
339
- chellow/templates/e/supplier_contract_edit.html,sha256=Afwtn0l8XRbt86bMOru2AjjvctoUkieD052aY0B2mDc,1463
338
+ chellow/templates/e/supplier_contract_edit.html,sha256=PciQ91n1PB2-Z8vK4HIRiAc4tH3zwV17tnA-T6sRJu0,1226
340
339
  chellow/templates/e/supplier_contracts.html,sha256=VwWD4q88Fynz7vioFSAsyH6RR_1SyQQl6bQwzL-W1m0,1508
341
340
  chellow/templates/e/supplier_element.html,sha256=XQXvSVFISC3ZwCRurfpZbppFJgMhzbhqcwDYFT50bok,1370
342
341
  chellow/templates/e/supplier_element_add.html,sha256=gSdn3X2BPUclPwbsuvQC-yIejaLykKFhJl_sC1kLB4s,1220
343
342
  chellow/templates/e/supplier_element_edit.html,sha256=NrW51twKITht7w4qi8xrxivU3h-jcGpYySBuxq-L5XU,1846
344
343
  chellow/templates/e/supplier_rate_script.html,sha256=tjWeCUAgNip3VLHzbXqe19Msiasys3Wm5Vra936qJjI,1245
345
344
  chellow/templates/e/supplier_rate_script_add.html,sha256=Yf2LZEIHbL7qT6oxBCtPf0ZX7vJsSo_ZeOKJhJoVh3o,690
346
- chellow/templates/e/supplier_rate_script_edit.html,sha256=VaYJt8nxHdnuP-zEAuBJC-ibEpjDU1b80hXtdBQH1dg,1968
345
+ chellow/templates/e/supplier_rate_script_edit.html,sha256=Nt2bg78DCXiQpl2SAxIWjJx2nqljOtcqKVDkUnmO9_k,1695
347
346
  chellow/templates/e/supply.html,sha256=kFyYPIzMgRZz7tHY_TfmjYzUkc1zXyaJses6X372Ozg,8427
348
347
  chellow/templates/e/supply_edit.html,sha256=2BRGU35nb0ZpUHKCi_fgAPMU6SlHI7ve3_Ne8LXHtms,2333
349
348
  chellow/templates/e/supply_eras.html,sha256=5lB7_oC-sTWRQoASuPgBWj5XSZa9ajR0s6V6xKHvf6E,19996
@@ -400,6 +399,7 @@ chellow/templates/g/supply_note_edit.html,sha256=b8mB6_ucBwoljp03iy6AgVaZUhGw3-1
400
399
  chellow/templates/g/supply_notes.html,sha256=6epNmZ3NKdXZz27fvmRUGeffg_oc1kmwuBeyRzQe3Rg,854
401
400
  chellow/templates/g/unit.html,sha256=KouNVU0-i84afANkLQ_heJ0uDfJ9H5A05PuLqb8iCN8,438
402
401
  chellow/templates/g/units.html,sha256=p5Nd-lAIboKPEOO6N451hx1bcKxMg4BDODnZ-43MmJc,441
403
- chellow-1760028799.0.0.dist-info/METADATA,sha256=zm1x1B_QH5ZI_HiJ4vFBX0RbyndLd_v2iw77jNS4QqU,12500
404
- chellow-1760028799.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
405
- chellow-1760028799.0.0.dist-info/RECORD,,
402
+ chellow/templates/reports/channel_snags.html,sha256=_aAgFgMlTkK2HuKFU8YisAIcUYfg6Hqhgyf5MZpdK8c,2629
403
+ chellow-1761297878.0.0.dist-info/METADATA,sha256=YJWJBO0S9POBAD-RG5IFnGyCsyecRLSoNm_xWroFfrw,12428
404
+ chellow-1761297878.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
405
+ chellow-1761297878.0.0.dist-info/RECORD,,
chellow/commands.py DELETED
@@ -1,52 +0,0 @@
1
- import argparse
2
- import os.path
3
- import signal
4
- import sys
5
- import time
6
- from os import environ
7
-
8
- from pep3143daemon import DaemonContext, PidFile
9
-
10
- import waitress
11
-
12
- from chellow import create_app
13
-
14
- app = create_app()
15
-
16
-
17
- def chellow_start(daemon):
18
- chellow_port = environ["CHELLOW_PORT"] if "CHELLOW_PORT" in environ else 80
19
- daemon.open()
20
- waitress.serve(app, host="0.0.0.0", port=chellow_port)
21
-
22
-
23
- def chellow_stop(pidfile_path):
24
- with open(pidfile_path) as pidfile:
25
- pid = int(pidfile.read())
26
- os.kill(pid, signal.SIGTERM)
27
- while os.path.exists(pidfile_path):
28
- time.sleep(1)
29
-
30
-
31
- def chellow_command():
32
- parser = argparse.ArgumentParser()
33
- parser.add_argument("action", choices=["start", "stop", "restart"])
34
- args = parser.parse_args()
35
-
36
- try:
37
- os.makedirs(app.instance_path)
38
- except BaseException:
39
- pass
40
- pidfile_path = os.path.join(app.instance_path, "chellow.pid")
41
- pidfile = PidFile(pidfile_path)
42
- daemon = DaemonContext(
43
- pidfile=pidfile, stdin=sys.stdin, stderr=sys.stderr, stdout=sys.stdout
44
- )
45
-
46
- if args.action == "start":
47
- chellow_start(daemon)
48
- elif args.action == "stop":
49
- chellow_stop(pidfile_path)
50
- elif args.action == "restart":
51
- chellow_stop(pidfile_path)
52
- chellow_start(daemon)
@@ -1,150 +0,0 @@
1
- import csv
2
- import sys
3
- import threading
4
- import traceback
5
-
6
- from dateutil.relativedelta import relativedelta
7
-
8
- from flask import g, redirect
9
-
10
- from sqlalchemy.orm import joinedload
11
- from sqlalchemy.sql.expression import false, select, true
12
-
13
- from chellow.dloads import open_file
14
- from chellow.models import (
15
- Channel,
16
- Contract,
17
- Era,
18
- Session,
19
- Site,
20
- SiteEra,
21
- Snag,
22
- Supply,
23
- User,
24
- )
25
- from chellow.utils import (
26
- csv_make_val,
27
- hh_before,
28
- req_bool,
29
- req_int,
30
- req_int_none,
31
- utc_datetime_now,
32
- )
33
-
34
-
35
- def content(contract_id, days_hidden, is_ignored, user_id):
36
- f = writer = None
37
- try:
38
- with Session() as sess:
39
- user = User.get_by_id(sess, user_id)
40
- if contract_id is None:
41
- contract = None
42
- namef = "all"
43
- else:
44
- contract = Contract.get_dc_by_id(sess, contract_id)
45
- namef = "".join(x if x.isalnum() else "_" for x in contract.name)
46
-
47
- f = open_file(f"channel_snags_{namef}.csv", user, mode="w", newline="")
48
- writer = csv.writer(f, lineterminator="\n")
49
- titles = (
50
- "contract",
51
- "Hidden Days",
52
- "Chellow Id",
53
- "Imp MPAN Core",
54
- "Exp MPAN Core",
55
- "Site Code",
56
- "Site Name",
57
- "Snag Description",
58
- "Import Related?",
59
- "Channel Type",
60
- "Start Date",
61
- "Finish Date",
62
- "Is Ignored?",
63
- "Days Since Snag Finished",
64
- "Duration Of Snag (Days)",
65
- )
66
- writer.writerow(titles)
67
-
68
- now = utc_datetime_now()
69
- cutoff_date = now - relativedelta(days=days_hidden)
70
- q = (
71
- select(Snag, Channel, Era, Supply, SiteEra, Site)
72
- .join(Channel, Snag.channel_id == Channel.id)
73
- .join(Era, Channel.era_id == Era.id)
74
- .join(Supply, Era.supply_id == Supply.id)
75
- .join(SiteEra, Era.site_eras)
76
- .join(Site, SiteEra.site_id == Site.id)
77
- .where(
78
- SiteEra.is_physical == true(),
79
- Snag.start_date < cutoff_date,
80
- )
81
- .order_by(
82
- Site.code,
83
- Supply.id,
84
- Channel.imp_related,
85
- Channel.channel_type,
86
- Snag.description,
87
- Snag.start_date,
88
- Snag.id,
89
- )
90
- .options(joinedload(Era.dc_contract))
91
- )
92
- if contract is not None:
93
- q = q.where(Era.dc_contract == contract)
94
-
95
- if not is_ignored:
96
- q = q.where(Snag.is_ignored == false())
97
-
98
- for snag, channel, era, supply, site_era, site in sess.execute(q):
99
- snag_start = snag.start_date
100
- snag_finish = snag.finish_date
101
- imp_mc = "" if era.imp_mpan_core is None else era.imp_mpan_core
102
- exp_mc = "" if era.exp_mpan_core is None else era.exp_mpan_core
103
-
104
- if snag_finish is None:
105
- duration = now - snag_start
106
- age_of_snag = None
107
- else:
108
- duration = snag_finish - snag_start
109
- if hh_before(cutoff_date, snag_finish):
110
- age_of_snag = None
111
- else:
112
- delta = now - snag_finish
113
- age_of_snag = delta.days
114
-
115
- vals = {
116
- "contract": era.dc_contract.name,
117
- "Hidden Days": days_hidden,
118
- "Chellow Id": snag.id,
119
- "Imp MPAN Core": imp_mc,
120
- "Exp MPAN Core": exp_mc,
121
- "Site Code": site.code,
122
- "Site Name": site.name,
123
- "Snag Description": snag.description,
124
- "Import Related?": channel.imp_related,
125
- "Channel Type": channel.channel_type,
126
- "Start Date": snag_start,
127
- "Finish Date": snag_finish,
128
- "Is Ignored?": snag.is_ignored,
129
- "Days Since Snag Finished": age_of_snag,
130
- "Duration Of Snag (Days)": duration.days,
131
- }
132
-
133
- writer.writerow(csv_make_val(vals[t]) for t in titles)
134
- except BaseException:
135
- msg = traceback.format_exc()
136
- sys.stderr.write(msg)
137
- writer.writerow([msg])
138
- finally:
139
- if f is not None:
140
- f.close()
141
-
142
-
143
- def do_get(sess):
144
- contract_id = req_int_none("dc_contract_id")
145
- days_hidden = req_int("days_hidden")
146
- is_ignored = req_bool("is_ignored")
147
-
148
- args = contract_id, days_hidden, is_ignored, g.user.id
149
- threading.Thread(target=content, args=args).start()
150
- return redirect("/downloads", 303)