Blame view

flaskr/models.py 7.11 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
    )
b41cd7ad   Antoine Goutenoir   chore: review models
45
46
    status = db.Column(db.Enum(StatusEnum), default=StatusEnum.pending)

c10e71f4   Antoine Goutenoir   Document the models.
47
    email = db.Column(db.Unicode(1024))
0d2f6665   Antoine Goutenoir   lint
48
49
50
51
    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.
52
53
54

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

322609d8   Antoine Goutenoir   Prepare the Emiss...
58
59
60
    # 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...
61
    # One slug per line (or blank char?)
466912a2   Antoine Goutenoir   Prepare the estim...
62
63
64
    models_slugs = db.Column(db.UnicodeText())

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

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

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

    @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...
87
            s += (u", " if s else u"") + self.institution
ac935afa   Antoine Goutenoir   Make warnings mor...
88
89
90
        return s

    @property
dc6abfd1   Antoine Goutenoir   Add a separate fi...
91
92
93
94
95
96
97
98
    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 ...
99
100
101
102
103
104
105
106
107
108
109
110
    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...
111
112
    def has_failed(self):
        return self.status == StatusEnum.failure
c10e71f4   Antoine Goutenoir   Document the models.
113

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

f48f4a8f   Antoine Goutenoir   Optimize a bottle...
119
120
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):
        # with shelve.open(filename=self.get_output_filename(), protocol=2) as shelf:
        #     shelf['output'] = 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
142
143
144
145
146
147
148
149
150
151
152

                    # 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
                    )
f48f4a8f   Antoine Goutenoir   Optimize a bottle...
153
                    self._output_dict = shelf['output']
f48f4a8f   Antoine Goutenoir   Optimize a bottle...
154
                    shelf.close()
9c034dd2   Antoine Goutenoir   fix: tweak shelve...
155
156

                    # mutex.release()
f48f4a8f   Antoine Goutenoir   Optimize a bottle...
157
158
                else:
                    self._output_dict = None
37e28f2c   Antoine Goutenoir   Improve resilience.
159
160
            else:
                self._output_dict = yaml_load(self.output_yaml)
f48f4a8f   Antoine Goutenoir   Optimize a bottle...
161
        return self._output_dict
a1f12452   Antoine Goutenoir   Add the new conte...
162

a1f12452   Antoine Goutenoir   Add the new conte...
163
164
165
    def is_one_to_one(self):
        return self.scenario == ScenarioEnum.one_to_one

a1f12452   Antoine Goutenoir   Add the new conte...
166
167
168
    def is_one_to_many(self):
        return self.scenario == ScenarioEnum.one_to_many

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

a1f12452   Antoine Goutenoir   Add the new conte...
172
173
    def is_many_to_many(self):
        return self.scenario == ScenarioEnum.many_to_many
f3694728   Antoine Goutenoir   Display a summary.
174

a3e9d0fc   Antoine Goutenoir   Fix home plot leg...
175
176
177
178
179
180
181
182
    _models = None

    def get_models(self):
        if self._models is None:
            mdl_slugs = self.models_slugs.split("\n")
            self._models = [m for m in models if m.slug in mdl_slugs]
        return self._models

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

ac935afa   Antoine Goutenoir   Make warnings mor...
184
185
# BACKOFFICE CONFIGURATION ####################################################

16f69d07   Antoine Goutenoir   Add an (unsecured...
186
187
188
189
class EstimationView(ModelView):
    # Show only name and email columns in list view
    column_list = (
        'public_id',
ac935afa   Antoine Goutenoir   Make warnings mor...
190
        'link',
8ae021a2   Antoine Goutenoir   Merge shelved cha...
191
        'run_name',
16f69d07   Antoine Goutenoir   Add an (unsecured...
192
        'status',
ac935afa   Antoine Goutenoir   Make warnings mor...
193
        'author_name',
466912a2   Antoine Goutenoir   Prepare the estim...
194
        'models_slugs',
a1f12452   Antoine Goutenoir   Add the new conte...
195
        'scenario',
dc6abfd1   Antoine Goutenoir   Add a separate fi...
196
197
        'origins_count',
        'destinations_count',
3d8865da   Antoine Goutenoir   Improve warnings ...
198
199
        'warnings_tail',
        'errors_tail',
16f69d07   Antoine Goutenoir   Add an (unsecured...
200
201
202
203
204
205
    )

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

ac935afa   Antoine Goutenoir   Make warnings mor...
206
    column_filters = ('first_name', 'last_name', 'status')
16f69d07   Antoine Goutenoir   Add an (unsecured...
207
208


c10e71f4   Antoine Goutenoir   Document the models.
209
210
# USERS #######################################################################

1b39d5ec   Goutte   Add a skeleton fo...
211
212
213
214
215
216
217
218
219
220
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
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...
246
        return '<User %r>' % self.username