-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Add feature flipping models to Projects, plus example feature flip #3194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Also, use a new pattern for API instance objects
a7b4595
to
59cccbf
Compare
Uh, on second though, I hit an issue where having a many2many relationship to projects makes more sense. I think i'll redo the relationship. I wanted to add a feature for defaulting to true before $start_date, so we'll want some extra fields on the feature and only 1 feature object for each feature added. I'm mostly certain the middle ground is not requiring data migrations, but for features like deprecating old code, we'll want to create a migration that immediately sets up the feature as true before $start_date. |
This allows for features on deprecated code, where we can say the feature is true historically.
Okay, this should be set. I've reworked the relationship, added feature filtering based on the feature add date, and added some new tests. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, other than a few small nits.
readthedocs/builds/models.py
Outdated
self.project = APIProject(**kwargs.pop('project', {})) | ||
# This was replicated from readthedocs.projects.utils.make_api_project. | ||
# I'm not certain why these need to be deleted | ||
for key in ['resource_uri', 'absolute_url', 'downloads']: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because they are fake objects that aren't on the model, but created by the API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could also invent the logic and delete anything that isn't a field on the project.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah gotcha, i recognized these as model attributes without looking. makes sense.
Ultimately, I think we want to some logic that uses the serializer to model these fields modeling for API consumption, and drop Project model proxying altogether. I'm just not sure what the connection to the serializer looks like yet. I think this is probably the best way to reduce duplication of data however.
requirements.extend(['sphinx==1.5.3', 'sphinx-rtd-theme<0.3', | ||
'readthedocs-sphinx-ext<0.6']) | ||
if self.project.has_feature(Feature.USE_SPHINX_LATEST): | ||
requirements.append('sphinx<1.7') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's the latest, shouldn't it just do sphinx
without a version ID?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was curious what we wanted to do here. I'm fine with leaving it bleeding edge, without a specifier. '1.7' specifier was a stopgap from making the version go too high.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe do semvar and <2
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah yeah, that's a really good idea. hopefully they follow semver as well. i'll do that instead of open ended on this and setuptools
PR.
readthedocs/projects/admin.py
Outdated
if feature.projects.count() > 10: | ||
projects += [ | ||
'... and {0} more'.format(feature.projects.count() - 10) | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Surprised this doesn't get done automatically. Does it output the whole list?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This view probably isn't useful in this view anyhow. I'll just simplify and make it a count.
'proxy': True, | ||
}, | ||
bases=('projects.project',), | ||
), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious that Django makes a migration for a proxy model. I assume it doesn't do anything to the DB?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://stackoverflow.com/questions/37988914/why-does-django-create-migration-files-for-proxy-models seems to say it's so later migrations can reference it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, i was surprised by this too at first, i didn't realize this when doing the integrations proxy models.
|
||
FEATURES = ( | ||
(USE_SPHINX_LATEST, _('Use latest version of Sphinx')), | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason we store these on the model? We usually have a constants file, so it seems odd to break that convention.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the prescribed method of defining constants:
https://docs.djangoproject.com/en/1.11/ref/models/fields/#django.db.models.Field.choices
readthedocs/projects/models.py
Outdated
) | ||
# Feature is not implemented as a ChoiceField, as we don't want validation | ||
# at the database level on this field. Arbitrary values are allowed here. | ||
feature = models.CharField( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feature.feature
is a weird name here. It should probably be id
or similar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feature_id
would be fine
def for_project(self, project): | ||
return self.filter( | ||
Q(projects=project) | | ||
Q(default_true=True, add_date__gt=project.pub_date) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clever.
👍 |
This add per-project feature flags that can be changed via the django admin. Features can be added via signal, though this is only required to list the feature in the admin dashboard forms, it isn't required to use the feature flags in code.
Because of some initial problems with modeling, this also updated the pattern we're using for Project/Version model instances in the build servers. These models make the relationship to the underlying data more explicit, that is, we're not using what looks to be a Project/Version, but rather a representation of the API return. I think this pattern does a better job of making this clear, though I think ultimately we want something completely divorced from the Project/Version models.
An example feature flip that we need is included for the Sphinx version.