this is a question about code style and best practices.
In our Django project we have Class Based Views named like this:
urls.py
path('project/<int:pk>/clone/', CloneView.as_view(), name='clone'),
path('project/<int:pk>/delete/', ProjectDelete.as_view(), name='project-delete'),
path('project/<int:pk>/optimize/', ProjectOptimize.as_view(), name='project-optimize'),
path('project/<int:pk>/report/', ReportView.as_view(), name='report'),
as you can see, some of them we say MyClassView.as_view() and in others we just say MyClass.as_view(). But I also noticed that in the Documentation they always use the first form: https://docs.djangoproject.com/en/2.2/topics/class-based-views/
Something that also caught my attention is that we never use view in the name, for example: name=clone-name.
My question is: is there any noticeable advantage of explicitly saying in the class name that this class is a view? Is it "wrong" not to use it?
I would like to keep our code base consistent.
Thank you all!
I don't think it really matters whether or not you include the View suffix. It's common to include the name in Django projects, but the most important thing it to be consistent across your own project.
One advantage of using the View or Form suffix is to avoid name clashes. For example BookView, BookForm and the model Book can't clash with each other.
You can avoid clashes by importing modules instead of objects, in which case the Form and View suffixes are not necessary.
Related
I have created custom Django model-field subclasses based on CharField but which use to_python() to ensure that the model objects returned have more complex objects (some are lists, some are dicts with a specific format, etc.) -- I'm using MySQL so some of the PostGreSql field types are not available.
All is working great, but Pylint believes that all values in these fields will be strings and thus I get a lot of "unsupported-membership-test" and "unsubscriptable-object" warnings on code that uses these models. I can disable these individually, but I would prefer to let Pylint know that these models return certain object types. Type hints are not helping, e.g.:
class MealPrefs(models.Model):
user = ...foreign key...
prefs: dict[str, list[str]] = \
custom_fields.DictOfListsExtendsCharField(
default={'breakfast': ['cereal', 'toast'],
'lunch': ['sandwich']},
)
I know that certain built-in Django fields return correct types for Pylint (CharField, IntegerField) and certain other extensions have figured out ways of specifying their type so Pylint is happy (MultiSelectField) but digging into their code, I can't figure out where the "magic" specifying the type returned would be.
(note: this question is not related to the INPUT:type of Django form fields)
Thanks!
I had a look at this out of curiosity, and I think most of the "magic" actually comes for pytest-django.
In the Django source code, e.g. for CharField, there is nothing that could really give a type hinter the notion that this is a string. And since the class inherits only from Field, which is also the parent of other non-string fields, the knowledge needs to be encoded elsewhere.
On the other hand, digging through the source code for pylint-django, though, I found where this most likely happens:
in pylint_django.transforms.fields, several fields are hardcoded in a similar fashion:
_STR_FIELDS = ('CharField', 'SlugField', 'URLField', 'TextField', 'EmailField',
'CommaSeparatedIntegerField', 'FilePathField', 'GenericIPAddressField',
'IPAddressField', 'RegexField', 'SlugField')
Further below, a suspiciously named function apply_type_shim, adds information to the class based on the type of field it is (either 'str', 'int', 'dict', 'list', etc.)
This additional information is passed to inference_tip, which according to the astroid docs, is used to add inference info (emphasis mine):
astroid can be used as more than an AST library, it also offers some
basic support of inference, it can infer what names might mean in a
given context, it can be used to solve attributes in a highly complex
class hierarchy, etc. We call this mechanism generally inference
throughout the project.
astroid is the underlying library used by Pylint to represent Python code, so I'm pretty sure that's how the information gets passed to Pylint. If you follow what happens when you import the plugin, you'll find this interesting bit in pylint_django/.plugin, where it actually imports the transforms, effectively adding the inference tip to the AST node.
I think if you want to achieve the same with your own classes, you could either:
Directly derive from another Django model class that already has the associated type you're looking for.
Create, and register an equivalent pylint plugin, that would also use Astroid to add information to the class so that Pylint know what to do with it.
I thought initially that you use a plugin pylint-django, but maybe you explicitly use prospector that automatically installs pylint-django if it finds Django.
The checker pylint neither its plugin doesn't check the code by use information from Python type annotations (PEP 484). It can parse a code with annotations without understanding them and e.g. not to warn about "unused-import" if a name is used in annotations only. The message unsupported-membership-test is reported in a line with expression something in object_A simply if the class A() doesn't have a method __contains__. Similarly the message unsubscriptable-object is related to method __getitem__.
You can patch pylint-django for your custom fields this way:
Add a function:
def my_apply_type_shim(cls, _context=None): # noqa
if cls.name == 'MyListField':
base_nodes = scoped_nodes.builtin_lookup('list')
elif cls.name == 'MyDictField':
base_nodes = scoped_nodes.builtin_lookup('dict')
else:
return apply_type_shim(cls, _context)
base_nodes = [n for n in base_nodes[1] if not isinstance(n, nodes.ImportFrom)]
return iter([cls] + base_nodes)
into pylint_django/transforms/fields.py
and also replace apply_type_shim by my_apply_type_shim in the same file at this line:
def add_transforms(manager):
manager.register_transform(nodes.ClassDef, inference_tip(my_apply_type_shim), is_model_or_form_field)
This adds base classes list or dict respectively, with their magic methods explained above, to your custom field classes if they are used in a Model or FormView.
Notes:
I thought also about a plugin stub solution that does the same, but the alternative with "prospector" seems so complicated for SO that I prefer to simply patch the source after installation.
Classes Model or FormView are the only classes created by metaclasses, used in Django. It is a great idea to emulate a metaclass by a plugin code and to control the analysis simple attributes. If I remember, MyPy, referenced in some comment here, has also a plugin mypy-django for Django, but only for FormView, because writing annotations for django.db is more complicated than to work with attributes. - I was trying to work on it for one week.
My problem is the following: For a while now I got used to encapsulate a few (the most relevant and reusable) queries of my application in properties within my Django models.
to do that, I constantly do:
from my_app.models import ModelBla
.....
class ModelBlehhh():
#property
def some_bla_things(self, bla):
return ModelBla.objects.filter(.....)
I have always considered it to be good practice, and I use most of them a lot throughout my applications.
The problem is: These imports that are being used mostly for querying are stating to get in the way of my models' relations structure. Meaning: It's becoming more and more frequent that I cannot create properties for querying without creating circular import issues.
Is my approach correct? Is there a better way to encapsulate these queries? What do you usually do?
Thanks for your help.
I don't see why you need the properties at all, let alone the imports.
If you're filtering some other model based on the current one, that must mean you have a relation to that model. And if you have a relation, then you should be using the automatic backwards relation. So rather than SomeOtherModel.objects.filter(blah=self), you should be doing self.someothermodel_set.all().
Is there a better way to encapsulate these queries?
Actually, you shouldn't let the circular dependency eat you, go removing it up instead. This will increase your access to the database in the later stage of the project.You can remove the circular imports by redefining your structure at this stage only , otherwise it will be too late to refactor and correct where you were wrong.
What do you usually do?
Basically, it is not what others do.It depends on what your application is scoped of. If your application is long term app, then what you are doing is wrong. Just make a repository kind of thing which will be available to all the code and the code can access it whenever needed without any circular imports , that is without any dependency.
I was reading over the following code, and the models were structured such that each class/model had a separate file and then it was imported in __init__.py. For example:
# __init__.py
from service import Service
from note import Note
etc...
# service.py (one example of the imports)
from django.db import models
class Service(models.Model):
#: service provider name (e.g. Hulu)
name = models.CharField(max_length=64, verbose_name="Title Name", unique=True)
def __unicode__(self):
return u'Service id=%s, name=%s' % (self.pk, self.name)
Which way is better practice, to have all models in one models.py file, or to have one file-per model? I usually keep all my models for an app in one file, and I had never seen the models separated, which is why I'm asking this question.
If you're talking true "best practice", that would be following the way Django recommends and using just models.py. However, there's a lot of opinion and argument that goes into the this topic. Nevertheless, my recommendations:
If you have a simple app, with only a few models. Stick with the "Django-way" of just a models.py.
If you have a huge app with lots of models and thousands of lines of code, divvying it out is probably better. However, at this point, you should also ask yourself why your app is so huge and if anything can be factored out into auxiliary apps.
Long and short, my personal opinion is that breaking out of the models into separate files is never a good idea. It can in some cases cause problems and I honestly can't see a use case where it's truly justified. Usually, if your app is big enough to warrant doing this, it's actually a sign that you're lumping too much functionality together that would be better delegated out into other apps.
Best practice is to put them in one file. Look at the django source for example.
The reason you've never seen it is because it's practically never done.
If you can justify it somehow then by all means do it, but it's definitely not the recommended structure. People start to explore splitting models when the file gets too large / can be logically separated.
I have seen people writing forms in models or in forms.py? Is it the case I can actually write it anywhere say in blah.py and just import it wherever necessary?
That's correct. It's just like any other python class, you can define your form classes in whichever file you prefer. However, it is often considered best practice for Django to put them in a file like APP/forms.py.
yes, you can put it anywhere provided that you can import it
You can, but it's better to put forms declarations in forms.py in order that other developers could feel familiar in your project.
This is really just a "best practices" question...
I find that When developing an app, I often end up with a lot of views.
Is it common practice to break these views up into several view files? In other words... instead of just having views.py, is it common to have views_1.py, views_2.py, views_3.py (but named more appropriately, perhaps by category)?
Splitting views.py
Most of your code probably expects your views to be accessible as myapp.views.viewname. One way I've seen people break up their views but keep this python name is to create a views/ directory. views/__init__.py will have:
from .foo_views import *
from .bar_views import *
from .baz_views import *
Then, in views/foo_views.py, put:
def foo_detail(request, ...):
# your code here
def foo_list(request, ...):
# your code here
def your_other_view(...):
# ...
etc. So you move everything from views.py into files in this directory, make __init__.py, delete views.py, and you're done.
Then, when you import myapp.views, myapp.views.foo_detail will refer to the function that you defined in views/foo_views.py.
Splitting other modules
This strategy should also work fine for admin.py, etc. But if you want to split up models.py like this, you will need to add app_label = 'your_app_name' to the class Meta: of all of your models. For example, unicorn_app/models/unicorns.py could have an entry like this:
class Unicorn(models.Model):
description = models.CharField(max_length=80)
class Meta:
app_label = 'unicorn_app'
(Otherwise, Django imagines that the Unicorn model is part of a Django app named "models", which messes up the admin site. Current through 1.6 - the upcoming 1.7 release will remove this requirement.)
As a general guideline, think about readability and maintainability: the default "views.py" is just a suggestion made by initial scaffolding - you do not have to stick to it.
Usually, files with thousands of lines of code are difficult to maintain, for this I usually try to decompose bigger modules into smaller ones.
On the other hand, the division should make sense - splitting related functions into several files, with lots of imports may make maintenance even more difficult.
Finally, you can also think about completely other ways to simplify your application.
Do you see duplicated code? Maybe some functionality could be moved in a completely different application? And so on.
Another option would be to move some of the functionality into one or more apps. This would allow you to move also forms and templates and keeping things structurized. You don't necessarily need to move the models which saves you from model and data migration.
For example you could have the following structure:
main_app/
|_models.py
|_views.py
|_forms.py
|_urls.py
|_templates/
sub_app_1/
|_views.py
|_forms.py
|_urls.py
|_templates/
sub_app_2/
|_views.py
|_forms.py
|_urls.py
|_templates/