Skip to content

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

Merged
merged 8 commits into from
Oct 31, 2017

Conversation

agjohnson
Copy link
Contributor

@agjohnson agjohnson commented Oct 25, 2017

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.

@agjohnson agjohnson added the PR: work in progress Pull request is not ready for full review label Oct 25, 2017
Also, use a new pattern for API instance objects
@agjohnson
Copy link
Contributor Author

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.

@agjohnson agjohnson added the PR: work in progress Pull request is not ready for full review label Oct 27, 2017
This allows for features on deprecated code, where we can say the feature is
true historically.
@agjohnson agjohnson removed the PR: work in progress Pull request is not ready for full review label Oct 27, 2017
@agjohnson
Copy link
Contributor Author

Okay, this should be set. I've reworked the relationship, added feature filtering based on the feature add date, and added some new tests.

Copy link
Member

@ericholscher ericholscher left a 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.

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']:
Copy link
Member

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.

Copy link
Member

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.

Copy link
Contributor Author

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')
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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?

Copy link
Contributor Author

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.

if feature.projects.count() > 10:
projects += [
'... and {0} more'.format(feature.projects.count() - 10)
]
Copy link
Member

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?

Copy link
Contributor Author

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',),
),
Copy link
Member

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?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

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')),
)
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

)
# 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(
Copy link
Member

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.

Copy link
Contributor Author

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clever.

@ericholscher
Copy link
Member

👍

@agjohnson agjohnson merged commit b65e2f3 into master Oct 31, 2017
@agjohnson agjohnson deleted the project-features branch October 31, 2017 05:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants