Blame view

flaskr/models.py 7.39 KB
9c034dd2   Antoine Goutenoir   fix: tweak shelve...
1
import enum
f48f4a8f   Antoine Goutenoir   Optimize a bottle...
2
3
import shelve
from os.path import join, isfile
16f69d07   Antoine Goutenoir   Add an (unsecured...
4

b41cd7ad   Antoine Goutenoir   chore: review models
5
from flask_admin.contrib.sqla import ModelView
1b39d5ec   Goutte   Add a skeleton fo...
6
from flask_login import UserMixin, AnonymousUserMixin
b41cd7ad   Antoine Goutenoir   chore: review models
7
from flask_sqlalchemy import SQLAlchemy
1b39d5ec   Goutte   Add a skeleton fo...
8
from werkzeug.security import generate_password_hash, check_password_hash
6a9e49da   Antoine Goutenoir   Load the output a...
9
from yaml import safe_load as yaml_load
f48f4a8f   Antoine Goutenoir   Optimize a bottle...
10

2b4cf2a1   Antoine Goutenoir   fix: use a qualif...
11
from flaskr.content import get_path, base_url
b41cd7ad   Antoine Goutenoir   chore: review models
12
from flaskr.core import generate_unique_id, models
f48f4a8f   Antoine Goutenoir   Optimize a bottle...
13

c10e71f4   Antoine Goutenoir   Document the models.
14
15
16
17
18
19
20
# These are not the emission "models" in the scientific meaning of the word.
# They are the SQL Database Models.
# These are also named Entities, in other conventions (we're following flasks")
# If you're looking for the Emission Models (aka scaling laws),
# look in `flaskr/laws/`.


1b39d5ec   Goutte   Add a skeleton fo...
21
22
23
db = SQLAlchemy()


c10e71f4   Antoine Goutenoir   Document the models.
24
25
class StatusEnum(enum.Enum):
    pending = 'pending'
03c194bf   Antoine Goutenoir   Actually implemen...
26
    working = 'working'
c10e71f4   Antoine Goutenoir   Document the models.
27
    success = 'success'
5b6d9d3d   Antoine Goutenoir   Add a method to t...
28
    failure = 'failure'
c10e71f4   Antoine Goutenoir   Document the models.
29
30


a1f12452   Antoine Goutenoir   Add the new conte...
31
32
33
34
35
36
37
class ScenarioEnum(enum.Enum):
    one_to_one = 'one_to_one'
    many_to_one = 'many_to_one'
    one_to_many = 'one_to_many'
    many_to_many = 'many_to_many'


c10e71f4   Antoine Goutenoir   Document the models.
38
39
class Estimation(db.Model):
    id = db.Column(db.Integer(), primary_key=True)
fa3d2b43   Antoine Goutenoir   Conflate the data...
40
41
42
43
44
    public_id = db.Column(
        db.Unicode(),
        default=lambda: generate_unique_id(),
        unique=True
    )
e1e633fa   Antoine Goutenoir   chore: improve er...
45
46

    unavailable_statuses = [StatusEnum.pending, StatusEnum.working]
b41cd7ad   Antoine Goutenoir   chore: review models
47
48
    status = db.Column(db.Enum(StatusEnum), default=StatusEnum.pending)

c10e71f4   Antoine Goutenoir   Document the models.
49
    email = db.Column(db.Unicode(1024))
0d2f6665   Antoine Goutenoir   lint
50
51
52
53
    first_name = db.Column(db.Unicode(1024))   # Antoine
    last_name = db.Column(db.Unicode(1024))    # Goutenoir
    institution = db.Column(db.Unicode(1024))  # IRAP
    run_name = db.Column(db.Unicode(1024))     # JPGU 2020
c10e71f4   Antoine Goutenoir   Document the models.
54
55
56

    # City, Country
    # One address per line
16f69d07   Antoine Goutenoir   Add an (unsecured...
57
58
    origin_addresses = db.Column(db.UnicodeText())
    destination_addresses = db.Column(db.UnicodeText())
c10e71f4   Antoine Goutenoir   Document the models.
59

322609d8   Antoine Goutenoir   Prepare the Emiss...
60
61
62
    # For (single, not round) trips below this distance, use the train
    use_train_below_km = db.Column(db.Integer())

a1f12452   Antoine Goutenoir   Add the new conte...
63
    # One slug per line (or blank char?)
466912a2   Antoine Goutenoir   Prepare the estim...
64
65
66
    models_slugs = db.Column(db.UnicodeText())

    # Deprecated, we detect this scenario from the amount of locations.
c10e71f4   Antoine Goutenoir   Document the models.
67
68
    compute_optimal_destination = db.Column(db.Boolean())

a1f12452   Antoine Goutenoir   Add the new conte...
69
70
    # Outputs
    scenario = db.Column(db.Enum(ScenarioEnum), default=ScenarioEnum.many_to_many)
dc6abfd1   Antoine Goutenoir   Add a separate fi...
71
72
    output_yaml = db.Column(db.UnicodeText())  # deprecated, use shelve file
    informations = db.Column(db.UnicodeText())
fa3d2b43   Antoine Goutenoir   Conflate the data...
73
74
75
    warnings = db.Column(db.UnicodeText())
    errors = db.Column(db.UnicodeText())

dc6abfd1   Antoine Goutenoir   Add a separate fi...
76
    @property
ac935afa   Antoine Goutenoir   Make warnings mor...
77
    def link(self):
381afb49   Antoine Goutenoir   Move the base URL...
78
79
        return u"%s/estimation/%s.html" \
               % (base_url, self.public_id)
ac935afa   Antoine Goutenoir   Make warnings mor...
80
81
82
83
84
85
86
87
88

    @property
    def author_name(self):
        s = u""
        if self.first_name:
            s += self.first_name
        if self.last_name:
            s += (u" " if s else u"") + self.last_name
        if self.institution:
381afb49   Antoine Goutenoir   Move the base URL...
89
            s += (u", " if s else u"") + self.institution
ac935afa   Antoine Goutenoir   Make warnings mor...
90
91
92
        return s

    @property
dc6abfd1   Antoine Goutenoir   Add a separate fi...
93
94
95
96
97
98
99
100
    def origins_count(self):
        return self.origin_addresses.strip().count("\n") + 1

    @property
    def destinations_count(self):
        return self.destination_addresses.strip().count("\n") + 1

    @property
3d8865da   Antoine Goutenoir   Improve warnings ...
101
102
103
104
105
106
107
108
109
110
111
112
    def errors_tail(self):
        return self.get_tail(self.errors)

    @property
    def warnings_tail(self):
        return self.get_tail(self.warnings)

    def get_tail(self, of_string, of_length=140):
        if not of_string:
            return ""
        return u"...%s" % of_string[-(min(of_length, len(of_string))):]

5b6d9d3d   Antoine Goutenoir   Add a method to t...
113
114
    def has_failed(self):
        return self.status == StatusEnum.failure
c10e71f4   Antoine Goutenoir   Document the models.
115

8ae021a2   Antoine Goutenoir   Merge shelved cha...
116
117
118
119
120
    def get_display_name(self):
        if self.run_name:
            return self.run_name
        return self.public_id

f48f4a8f   Antoine Goutenoir   Optimize a bottle...
121
122
123
124
125
    def get_output_filename(self):
        runs_dir = get_path("var/runs")
        return join(runs_dir, self.public_id)

    def set_output_dict(self, output):
9c034dd2   Antoine Goutenoir   fix: tweak shelve...
126
127
128
129
130
        shelf = shelve.open(
            filename=self.get_output_filename(),
            flag='c',  # read/write, create if needed
            protocol=2
        )
f48f4a8f   Antoine Goutenoir   Optimize a bottle...
131
132
133
        shelf['output'] = output
        shelf.close()

a3e9d0fc   Antoine Goutenoir   Fix home plot leg...
134
135
    _output_dict = None

6a9e49da   Antoine Goutenoir   Load the output a...
136
    def get_output_dict(self):
a3e9d0fc   Antoine Goutenoir   Fix home plot leg...
137
        if self._output_dict is None:
ca35720c   Antoine Goutenoir   Fix typo.
138
            if self.output_yaml is None:
f48f4a8f   Antoine Goutenoir   Optimize a bottle...
139
140
                output_filename = self.get_output_filename()
                if isfile(output_filename):
9c034dd2   Antoine Goutenoir   fix: tweak shelve...
141

e1e633fa   Antoine Goutenoir   chore: improve er...
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
                    try:
                        # Perhaps we'll need a mutex around here
                        # from threading import Lock
                        # mutex = Lock()
                        # mutex.acquire()

                        # Not using the `with …` syntax, but we may in python3
                        shelf = shelve.open(
                            filename=output_filename,
                            flag='r',
                            protocol=2
                        )
                        self._output_dict = shelf['output']
                        shelf.close()

                        # mutex.release()
                    except Exception as e:

                        return None
f48f4a8f   Antoine Goutenoir   Optimize a bottle...
161
162
                else:
                    self._output_dict = None
37e28f2c   Antoine Goutenoir   Improve resilience.
163
164
            else:
                self._output_dict = yaml_load(self.output_yaml)
f48f4a8f   Antoine Goutenoir   Optimize a bottle...
165
        return self._output_dict
a1f12452   Antoine Goutenoir   Add the new conte...
166

a1f12452   Antoine Goutenoir   Add the new conte...
167
168
169
    def is_one_to_one(self):
        return self.scenario == ScenarioEnum.one_to_one

a1f12452   Antoine Goutenoir   Add the new conte...
170
171
172
    def is_one_to_many(self):
        return self.scenario == ScenarioEnum.one_to_many

a1f12452   Antoine Goutenoir   Add the new conte...
173
174
    def is_many_to_one(self):
        return self.scenario == ScenarioEnum.many_to_one
6a9e49da   Antoine Goutenoir   Load the output a...
175

a1f12452   Antoine Goutenoir   Add the new conte...
176
177
    def is_many_to_many(self):
        return self.scenario == ScenarioEnum.many_to_many
f3694728   Antoine Goutenoir   Display a summary.
178

a3e9d0fc   Antoine Goutenoir   Fix home plot leg...
179
180
181
182
    _models = None

    def get_models(self):
        if self._models is None:
e1e633fa   Antoine Goutenoir   chore: improve er...
183
184
            slugs = self.models_slugs.split("\n")
            self._models = [m for m in models if m.slug in slugs]
a3e9d0fc   Antoine Goutenoir   Fix home plot leg...
185
186
        return self._models

e1e633fa   Antoine Goutenoir   chore: improve er...
187
188
189
190
191
192
    def is_available(self):
        if self.status in self.unavailable_statuses:
            return False
        # We might add more conditions here, such as file data availability
        return True

6a9e49da   Antoine Goutenoir   Load the output a...
193

ac935afa   Antoine Goutenoir   Make warnings mor...
194
195
# BACKOFFICE CONFIGURATION ####################################################

16f69d07   Antoine Goutenoir   Add an (unsecured...
196
197
198
199
class EstimationView(ModelView):
    # Show only name and email columns in list view
    column_list = (
        'public_id',
ac935afa   Antoine Goutenoir   Make warnings mor...
200
        'link',
8ae021a2   Antoine Goutenoir   Merge shelved cha...
201
        'run_name',
16f69d07   Antoine Goutenoir   Add an (unsecured...
202
        'status',
ac935afa   Antoine Goutenoir   Make warnings mor...
203
        'author_name',
466912a2   Antoine Goutenoir   Prepare the estim...
204
        'models_slugs',
a1f12452   Antoine Goutenoir   Add the new conte...
205
        'scenario',
dc6abfd1   Antoine Goutenoir   Add a separate fi...
206
207
        'origins_count',
        'destinations_count',
3d8865da   Antoine Goutenoir   Improve warnings ...
208
209
        'warnings_tail',
        'errors_tail',
16f69d07   Antoine Goutenoir   Add an (unsecured...
210
211
212
213
214
215
    )

    # Enable search functionality - it will search for terms in
    # name and email fields
    # column_searchable_list = ('name', 'email')

ac935afa   Antoine Goutenoir   Make warnings mor...
216
    column_filters = ('first_name', 'last_name', 'status')
16f69d07   Antoine Goutenoir   Add an (unsecured...
217
218


c10e71f4   Antoine Goutenoir   Document the models.
219
220
# USERS #######################################################################

1b39d5ec   Goutte   Add a skeleton fo...
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
class User(db.Model, UserMixin):
    id = db.Column(db.Integer(), primary_key=True)
    username = db.Column(db.String())
    password = db.Column(db.String())

    def __init__(self, username, password):
        self.username = username
        self.set_password(password)

    def set_password(self, password):
        self.password = generate_password_hash(password)

    def check_password(self, value):
        return check_password_hash(self.password, value)

    @property
    def is_authenticated(self):
        if isinstance(self, AnonymousUserMixin):
            return False
        else:
            return True

    def is_active(self):
        return True

    def is_anonymous(self):
        if isinstance(self, AnonymousUserMixin):
            return True
        else:
            return False

    def get_id(self):
        return self.id

    def __repr__(self):
fa3d2b43   Antoine Goutenoir   Conflate the data...
256
        return '<User %r>' % self.username