Skip to content
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

Fix handling of deferred fields on django 1.10+ #317

Merged
merged 18 commits into from
Jul 2, 2018
Merged

Fix handling of deferred fields on django 1.10+ #317

merged 18 commits into from
Jul 2, 2018

Conversation

lucaswiman
Copy link
Contributor

@lucaswiman lucaswiman commented Apr 3, 2018

Fixes #278. cc @utapyngo @djstein

Problem

Prior to Django 1.10, deferred attributes were handled by constructing a new model class with custom descriptors for the deferred attributes. After 1.10, deferred fields are tracked by whether the attribute is present in the instance __dict__.

FieldTracker handled tracking on these fields by overwriting the descriptors on the model class. This means that in 1.10+, the instance model class is the model, so FieldTracker can introduce changes to the base class whenever deferred fields are used that persist on other queries. #278 This is reproduced in a test case.

Solution

I updated the finalize_class method to wrap field descriptors. This preserves the custom descriptor behavior, and means that deferring fields does not lead to permanent changes in base model classes. This is only done for django versions after 1.10: the previous behavior is preserved for 1.8 and 1.9.

Since the relevant code branches behavior on unsupported versions of Django, I also removed all branching behavior for versions of django listed as unsupported in the CHANGELOG.

Commandments

  • Write PEP8 compliant code.
  • Cover it with tests. Reproduced in test cases, which fail prior to 54cc150 and pass afterwards.
  • Update CHANGES.rst file to describe the changes, and quote according issue with GH-<issue_number>.
  • Pay attention to backward compatibility, or if it breaks it, explain why. The only backwards incompatible change is that the tracker._deferred_fields attribute is not set on django versions >=1.10. Since _deferred_fields is a private attribute, I wouldn't consider this a breaking change.
  • Update documentation (if relevant). _I don't think it is relevant.

Regarding PEP8, I ran flake8, and it generated a number of errors. I think my code didn't introduce new PEP8 violations, but I'm not sure. If you want, I can submit a separate PR fixing all the PEP8 violations, and rebase onto this branch.

@codecov
Copy link

codecov bot commented Apr 3, 2018

Codecov Report

Merging #317 into master will increase coverage by 3.61%.
The diff coverage is 96.87%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #317      +/-   ##
==========================================
+ Coverage   94.81%   98.42%   +3.61%     
==========================================
  Files           2        2              
  Lines         212      254      +42     
==========================================
+ Hits          201      250      +49     
+ Misses         11        4       -7
Impacted Files Coverage Δ
model_utils/tracker.py 97.86% <96.87%> (+5.44%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e750fc7...2e92877. Read the comment docs.

@lucaswiman lucaswiman changed the title Django 1.11 compatibility Fix handling of deferred fields on django 1.10+ Apr 3, 2018
@lucaswiman
Copy link
Contributor Author

😬 I'll investigate why code coverage went down by so much.

@lucaswiman
Copy link
Contributor Author

I fixed a few coverage issues, but I think maybe the codecov integration isn't running the full test suite? All of the code which only runs on older versions of django shows as "not covered". I think this is an artifact of the coverage setup, and not reflective of an inadequacy in the tests I added.

@lucaswiman
Copy link
Contributor Author

Hmm, I wish I'd noticed #313 existed before starting on this. cc @jcushman

I think we're fixing different bugs with the handling of deferred fields, but the patches are slightly incompatible.

lucaswiman and others added 9 commits June 21, 2018 12:41
Support for older versions of django was dropped in 3.0.0.
This commit adds a collection of wrapper classes for tracking fields
while still using custom descriptors that may be present. This fixes
a bug where deferring a model field with a custom descriptor meant
that the descriptor was overridden in all subsequent queries.
As far as I can tell, no changes to documentation is required.
The complications are that when the attribute is set in Django 1.10,
it no longer counts as a deferred attribute, and it is not retrieved from the database.
Naively updating __set__ to retrieve the value if it is deferred leads to infinite
recursion because accessing the attribute involves loading data from the database
and trying to set the attribute based on that value. This commit introduces
a somewhat hacky flag that records whether we're already trying to set
the attribute further up in the call stack.
This should maintain compatibility with the next version of django.
@lucaswiman
Copy link
Contributor Author

@jcushman I think this now includes fixes from #313, updated to work on Django 1.10+. Could you take a look?

@carljm What is required to get this merged and deployed this to pypi?

Lucas Wiman added 4 commits June 28, 2018 13:41
Hopefully this will combine the coverage reports.
… all tox environments run on the travis environment.

Note that it is run as one per python version, but multiple versions of django are tested in each.
@lucaswiman
Copy link
Contributor Author

OK, I got the codecov checks passing by correctly aggregating the different test environments and adding a few assertions to the tests.

@jcushman
Copy link
Contributor

Hi! I don't have my head super in this, but I see that my tests are there and passing, which is great. 👍

It sounds like you can join Jazzband and merge this yourself, if you're comfortable with the code at this point: #279 (comment)

Alternatively you might @ a few folks who have merged PRs in the last year for another pair of eyes.

@jcushman
Copy link
Contributor

(Oh one nitpick, looks like the merge on CHANGES.rst put a line in the wrong place.)

@lucaswiman
Copy link
Contributor Author

you might @ a few folks who have merged PRs in the last year for another pair of eyes.

@tony Would you care to take a look?

@tony
Copy link
Member

tony commented Jul 1, 2018

@jcushman @lucaswiman I don't use the deferred fields part of django-model-utils.

I breezed through the code and it LGTM. I think that's the best I can offer.

I don't want to offer more since I don't have a Django website using that part of django-model-utils to test on fully.

@lucaswiman
Copy link
Contributor Author

Thanks @tony and @jcushman! I'll merge this and fix pep8 in another PR.

@lucaswiman lucaswiman merged commit 16dec4d into jazzband:master Jul 2, 2018
@lucaswiman lucaswiman deleted the django-1.11-compatibility branch July 2, 2018 18:30
@lucaswiman lucaswiman mentioned this pull request Jul 2, 2018
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.

AttributeError: 'CountryDescriptor' object has no attribute 'field_name'
3 participants