Blame view

flaskr/models.py 6.86 KB
f48f4a8f   Antoine Goutenoir   Optimize a bottle...
1
2
import shelve
from os.path import join, isfile
16f69d07   Antoine Goutenoir   Add an (unsecured...
3

b41cd7ad   Antoine Goutenoir   chore: review models
4
5
import enum
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

381afb49   Antoine Goutenoir   Move the base URL...
11
from 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
126
127
128
129
    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
        shelf = shelve.open(filename=self.get_output_filename(), protocol=2)
        shelf['output'] = output
        shelf.close()

a3e9d0fc   Antoine Goutenoir   Fix home plot leg...
130
131
    _output_dict = None

6a9e49da   Antoine Goutenoir   Load the output a...
132
    def get_output_dict(self):
a3e9d0fc   Antoine Goutenoir   Fix home plot leg...
133
        if self._output_dict is None:
ca35720c   Antoine Goutenoir   Fix typo.
134
            if self.output_yaml is None:
f48f4a8f   Antoine Goutenoir   Optimize a bottle...
135
136
137
138
139
140
141
142
143
144
145
                output_filename = self.get_output_filename()
                if isfile(output_filename):
                    # with shelve.open(filename=output_filename,
                    #                  protocol=2) as shelf:
                    #     self._output_dict = shelf['output']
                    shelf = shelve.open(filename=output_filename, protocol=2)
                    self._output_dict = shelf['output']
                    # self._output_dict = copy(shelf['output'])
                    shelf.close()
                else:
                    self._output_dict = None
37e28f2c   Antoine Goutenoir   Improve resilience.
146
147
            else:
                self._output_dict = yaml_load(self.output_yaml)
f48f4a8f   Antoine Goutenoir   Optimize a bottle...
148
        return self._output_dict
a1f12452   Antoine Goutenoir   Add the new conte...
149

a1f12452   Antoine Goutenoir   Add the new conte...
150
151
152
    def is_one_to_one(self):
        return self.scenario == ScenarioEnum.one_to_one

a1f12452   Antoine Goutenoir   Add the new conte...
153
154
155
    def is_one_to_many(self):
        return self.scenario == ScenarioEnum.one_to_many

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

a1f12452   Antoine Goutenoir   Add the new conte...
159
160
    def is_many_to_many(self):
        return self.scenario == ScenarioEnum.many_to_many
f3694728   Antoine Goutenoir   Display a summary.
161

a3e9d0fc   Antoine Goutenoir   Fix home plot leg...
162
163
164
165
166
167
168
169
    _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...
170

ac935afa   Antoine Goutenoir   Make warnings mor...
171
172
# BACKOFFICE CONFIGURATION ####################################################

16f69d07   Antoine Goutenoir   Add an (unsecured...
173
174
175
176
class EstimationView(ModelView):
    # Show only name and email columns in list view
    column_list = (
        'public_id',
ac935afa   Antoine Goutenoir   Make warnings mor...
177
        'link',
8ae021a2   Antoine Goutenoir   Merge shelved cha...
178
        'run_name',
16f69d07   Antoine Goutenoir   Add an (unsecured...
179
        'status',
ac935afa   Antoine Goutenoir   Make warnings mor...
180
        'author_name',
466912a2   Antoine Goutenoir   Prepare the estim...
181
        'models_slugs',
a1f12452   Antoine Goutenoir   Add the new conte...
182
        'scenario',
dc6abfd1   Antoine Goutenoir   Add a separate fi...
183
184
        'origins_count',
        'destinations_count',
3d8865da   Antoine Goutenoir   Improve warnings ...
185
186
        'warnings_tail',
        'errors_tail',
16f69d07   Antoine Goutenoir   Add an (unsecured...
187
188
189
190
191
192
    )

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

ac935afa   Antoine Goutenoir   Make warnings mor...
193
    column_filters = ('first_name', 'last_name', 'status')
16f69d07   Antoine Goutenoir   Add an (unsecured...
194
195


c10e71f4   Antoine Goutenoir   Document the models.
196
197
# USERS #######################################################################

1b39d5ec   Goutte   Add a skeleton fo...
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
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...
233
        return '<User %r>' % self.username