| 13 | | == Models support properties == |
| 14 | | |
| 15 | | '''Status: Done''' |
| 16 | | |
| 17 | | Unlike before, properties are supported on models. |
| 18 | | |
| 19 | | {{{ |
| 20 | | #!python |
| 21 | | from django.db import models |
| 22 | | |
| 23 | | class Person(models.Model): |
| 24 | | first_name = models.CharField(maxlength=30) |
| 25 | | last_name = models.CharField(maxlength=30) |
| 26 | | |
| 27 | | def _get_full_name(self): |
| 28 | | return "%s %s" % (self.first_name, self.last_name) |
| 29 | | full_name = property(_get_full_name) |
| 30 | | }}} |
| 31 | | |
| 32 | | == Model class and Field classes renamed/relocated == |
| 33 | | |
| 34 | | '''Status: Done''' |
| 35 | | |
| 36 | | Difference: Import is from {{{django.db.models}}} instead of {{{django.core.meta}}}. This is easier to remember. |
| 37 | | |
| 38 | | {{{ |
| 39 | | #!python |
| 40 | | from django.db import models |
| 41 | | |
| 42 | | class Person(models.Model): |
| 43 | | first_name = models.CharField(maxlength=30) |
| 44 | | last_name = models.CharField(maxlength=30) |
| 45 | | }}} |
| 46 | | |
| 47 | | == Database connection relocated/renamed == |
| 48 | | |
| 49 | | '''Status: Done''' |
| 50 | | |
| 51 | | The connection is now available at {{{django.db.connection}}}. This is easier to remember, clearer and more consistent. |
| 52 | | |
| 53 | | Old: |
| 54 | | {{{ |
| 55 | | #!python |
| 56 | | from django.core.db import db |
| 57 | | cursor = db.cursor() |
| 58 | | }}} |
| 59 | | |
| 60 | | New: |
| 61 | | {{{ |
| 62 | | #!python |
| 63 | | from django.db import connection |
| 64 | | cursor = connection.cursor() |
| 65 | | }}} |
| 66 | | |
| 67 | | Backend-specific functions, if you should need them, are available at {{{django.db.backend}}}. |
| 68 | | |
| 69 | | Old: |
| 70 | | {{{ |
| 71 | | #!python |
| 72 | | from django.core import db |
| 73 | | db.quote_name('foo') |
| 74 | | }}} |
| 75 | | |
| 76 | | New: |
| 77 | | {{{ |
| 78 | | #!python |
| 79 | | from django.db import backend |
| 80 | | backend.quote_name('foo') |
| 81 | | }}} |
| 82 | | |
| 83 | | Also, the various backend functionality has been split into three separate modules for each backend -- {{{base.py}}}, {{{creation.py}}} and {{{introspection.py}}}. This is purely for performance and memory savings, so that basic, everyday Django usage doesn't have to load the introspective functionality into memory. |
| 84 | | |
| 85 | | == Interact directly with model classes, not with magic modules == |
| 86 | | |
| 87 | | '''Status: Done''' |
| 88 | | |
| 89 | | Import the model class directly from the module in which it was defined. No more {{{django.models.*}}} magic. |
| 90 | | |
| 91 | | {{{ |
| 92 | | #!python |
| 93 | | from myproject.people.models import Person |
| 94 | | p = Person(first_name='John', last_name='Smith') |
| 95 | | p.save() |
| 96 | | }}} |
| 97 | | |
| 98 | | This also removes the need for the {{{module_name}}} parameter. |
| 99 | | |
| 100 | | == Access table-level DB API functions via model classes, not with magic modules == |
| 101 | | |
| 102 | | '''Status: Done''' |
| 103 | | |
| 104 | | All "table-level" functions -- ways of retrieving records tablewide rather than performing instance-specific tasks -- are now accessed via a model class's {{{objects}}} attribute. They aren't direct methods of a model instance object because we want to keep the "table-wide" and "row-specific" namespaces separate. |
| 105 | | |
| 106 | | {{{ |
| 107 | | #!python |
| 108 | | from myproject.people.models import Person |
| 109 | | p_list = Person.objects.get_list() |
| 110 | | p = Person.objects.get_object() |
| 111 | | }}} |
| 112 | | |
| 113 | | This doesn't work from an instance. |
| 114 | | |
| 115 | | {{{ |
| 116 | | #!python |
| 117 | | p = Person.objects.get_object(pk=1) |
| 118 | | p.objects.get_list() # Raises AttributeError |
| 119 | | }}} |
| 120 | | |
| 121 | | == Override default manager name ("objects") == |
| 122 | | |
| 123 | | '''Status: Done''' |
| 124 | | |
| 125 | | If a model already has an {{{objects}}} attribute, you'll need to specify an alternate name for the magic {{{objects}}}. |
| 126 | | |
| 127 | | {{{ |
| 128 | | #!python |
| 129 | | class Person(models.Model): |
| 130 | | first_name = models.CharField(maxlength=30) |
| 131 | | last_name = models.CharField(maxlength=30) |
| 132 | | objects = models.TextField() |
| 133 | | people = models.Manager() |
| 134 | | |
| 135 | | p = Person(first_name='Mary', last_name='Jones', objects='Hello there.') |
| 136 | | p.save() |
| 137 | | p.objects == 'Hello there.' |
| 138 | | Person.people.get_list() |
| 139 | | }}} |
| 140 | | |
| 141 | | == Custom managers, and multiple managers == |
| 142 | | |
| 143 | | '''Status: Done''' |
| 144 | | |
| 145 | | You can create as many managers as you want. When necessary (such as on the admin), Django will use the first one defined, in order. |
| 146 | | |
| 147 | | If you define at least one custom manager, it will not get the default "objects" manager. |
| 148 | | |
| 149 | | {{{ |
| 150 | | #!python |
| 151 | | class Person(models.Model): |
| 152 | | first_name = models.CharField(maxlength=30) |
| 153 | | last_name = models.CharField(maxlength=30) |
| 154 | | people = models.Manager() |
| 155 | | fun_people = SomeOtherManager() |
| 156 | | }}} |
| 157 | | |
| 158 | | == Added a more powerful way of overriding model methods, removed hard-coded _pre_save(), _post_save(), etc. == |
| 159 | | |
| 160 | | '''Status: Done''' |
| 161 | | |
| 162 | | Proper subclassing of methods now works, so you can subclass the automatic {{{save()}}} and {{{delete()}}} methods. This removes the need for the {{{_pre_save()}}}, {{{_post_save()}}}, {{{_pre_delete()}}} and {{{_post_delete()}}} hooks -- all of which have been removed. Example: |
| 163 | | |
| 164 | | {{{ |
| 165 | | #!python |
| 166 | | class Person(models.Model): |
| 167 | | first_name = models.CharField(maxlength=30) |
| 168 | | last_name = models.CharField(maxlength=30) |
| 169 | | |
| 170 | | def save(self): |
| 171 | | self.do_something() |
| 172 | | super(Person, self).save() # Call the "real" save() method. |
| 173 | | self.do_something_else() |
| 174 | | }}} |
| 175 | | |
| 176 | | You can even skip saving (as requested in #1014). |
| 177 | | |
| 178 | | {{{ |
| 179 | | #!python |
| 180 | | class Person(models.Model): |
| 181 | | first_name = models.CharField(maxlength=30) |
| 182 | | last_name = models.CharField(maxlength=30) |
| 183 | | |
| 184 | | def save(self): |
| 185 | | if datetime.date.today() > datetime.date(2005, 1, 1): |
| 186 | | super(Person, self).save() # Call the "real" save() method. |
| 187 | | else: |
| 188 | | # Don't save. |
| 189 | | pass |
| 190 | | }}} |
| 191 | | |
| 192 | | == New API functionality: Overriding table-level functions == |
| 193 | | |
| 194 | | '''Status: Done''' |
| 195 | | |
| 196 | | You can override any table-level functions, such as {{{get_list()}}} or {{{get_object()}}}. Do this by creating a custom {{{models.Manager}}} subclass and passing it to your model. |
| 197 | | |
| 198 | | {{{ |
| 199 | | #!python |
| 200 | | from django.db import models |
| 201 | | class PersonManager(models.Manager): |
| 202 | | def get_list(self, **kwargs): |
| 203 | | # Changes get_list() to hard-code a limit=10. |
| 204 | | kwargs['limit'] = 10 |
| 205 | | return models.Manager.get_list(self, **kwargs) # Call the "real" get_list() method. |
| 206 | | |
| 207 | | class Person(models.Model): |
| 208 | | first_name = models.CharField(maxlength=30) |
| 209 | | last_name = models.CharField(maxlength=30) |
| 210 | | objects = PersonManager() |
| 211 | | }}} |
| 212 | | |
| 213 | | If a manager needs to access its associated class, it should use {{{self.klass}}}. Example: |
| 214 | | |
| 215 | | {{{ |
| 216 | | #!python |
| 217 | | class PersonManager(models.Manager): |
| 218 | | def get_fun_person(self): |
| 219 | | try: |
| 220 | | return self.get_object(fun__exact=True) |
| 221 | | except self.klass.DoesNotExist: |
| 222 | | print "Doesn't exist." |
| 223 | | }}} |
| 224 | | |
| 225 | | An alternative syntax for this is proposed in #1224. |
| 226 | | |
| 227 | | == Renamed !DoesNotExist exception == |
| 228 | | |
| 229 | | '''Status: Done''' |
| 230 | | |
| 231 | | Instead of {{{people.PersonDoesNotExist}}}, it's {{{Person.DoesNotExist}}}. |
| 232 | | |
| 233 | | Old: |
| 234 | | {{{ |
| 235 | | #!python |
| 236 | | from django.models.myapp import people |
| 237 | | try: |
| 238 | | people.get_object(pk=1) |
| 239 | | except people.PersonDoesNotExist: |
| 240 | | print "Not there" |
| 241 | | }}} |
| 242 | | |
| 243 | | New: |
| 244 | | {{{ |
| 245 | | #!python |
| 246 | | from path.to.myapp.models import Person |
| 247 | | try: |
| 248 | | Person.objects.get_object(pk=1) |
| 249 | | except Person.DoesNotExist: |
| 250 | | print "Not there" |
| 251 | | }}} |
| 252 | | |
| 253 | | == Removed !SilentVariableFailure exception == |
| 254 | | |
| 255 | | '''Status: Done''' |
| 256 | | |
| 257 | | Old behavior: Any exception that subclasses {{{django.core.template.SilentVariableFailure}}} fails silently in the template system. |
| 258 | | |
| 259 | | New behavior: Any exception that has a {{{silent_variable_failure}}} attribute fails silently in the template system. {{{django.core.template.SilentVariableFailure}}} no longer exists. |
| 260 | | |
| 261 | | == Automatic manipulators == |
| 262 | | |
| 263 | | '''Status: Mostly done, with some quirks left''' |
| 264 | | |
| 265 | | Old: |
| 266 | | {{{ |
| 267 | | #!python |
| 268 | | from django.models.myapp import people |
| 269 | | m1 = people.AddManipulator() |
| 270 | | m2 = people.ChangeManipulator(3) |
| 271 | | }}} |
| 272 | | |
| 273 | | New: |
| 274 | | {{{ |
| 275 | | #!python |
| 276 | | from path.to.myapp.models import Person |
| 277 | | m1 = Person.AddManipulator() |
| 278 | | m2 = Person.ChangeManipulator(3) |
| 279 | | }}} |
| 280 | | |
| 281 | | == Renamed 'class META' to 'class Meta' == |
| 282 | | |
| 283 | | '''Status: Done''' |
| 284 | | |
| 285 | | The {{{class META}}} within models should now be {{{class Meta}}}. The latter is nicer on the eyes. |
| 286 | | |
| 287 | | == Moved admin options to 'class Admin' == |
| 288 | | |
| 289 | | '''Status: Done''' |
| 290 | | |
| 291 | | Instead of {{{admin=meta.Admin}}} in the {{{class META}}}, all admin options are in an inner {{{class Admin}}}. |
| 292 | | |
| 293 | | Old: |
| 294 | | {{{ |
| 295 | | #!python |
| 296 | | class Person(meta.Model): |
| 297 | | first_name = meta.CharField(maxlength=30) |
| 298 | | last_name = meta.CharField(maxlength=30) |
| 299 | | class META: |
| 300 | | admin = meta.Admin( |
| 301 | | list_display = ('first_name', 'last_name') |
| 302 | | ) |
| 303 | | }}} |
| 304 | | |
| 305 | | New: |
| 306 | | {{{ |
| 307 | | #!python |
| 308 | | class Person(models.Model): |
| 309 | | first_name = models.CharField(maxlength=30) |
| 310 | | last_name = models.CharField(maxlength=30) |
| 311 | | class Admin: |
| 312 | | list_display = ('first_name', 'last_name') |
| 313 | | }}} |
| 314 | | |
| 315 | | == get_object_or_404 and get_list_or_404 now take model classes, not modules == |
| 316 | | |
| 317 | | '''Status: Done''' |
| 318 | | |
| 319 | | Old: |
| 320 | | {{{ |
| 321 | | #!python |
| 322 | | get_object_or_404(polls, pk=1) |
| 323 | | }}} |
| 324 | | |
| 325 | | New: |
| 326 | | {{{ |
| 327 | | #!python |
| 328 | | get_object_or_404(Poll, pk=1) |
| 329 | | }}} |
| 330 | | |
| 331 | | == Model methods no longer automatically have access to datetime and db modules == |
| 332 | | |
| 333 | | '''Status: Done''' |
| 334 | | |
| 335 | | Formerly, each model method magically had access to the {{{datetime}}} module and to the variable {{{db}}}, which represents the current database connection. Now, those have to be imported explicitly. |
| 336 | | |
| 337 | | Old: |
| 338 | | {{{ |
| 339 | | #!python |
| 340 | | def some_method(self): |
| 341 | | print datetime.datetime.now() |
| 342 | | cursor = db.cursor() |
| 343 | | cursor.execute("UPDATE something;") |
| 344 | | }}} |
| 345 | | |
| 346 | | New: |
| 347 | | {{{ |
| 348 | | #!python |
| 349 | | import datetime |
| 350 | | from django.db import connection |
| 351 | | |
| 352 | | # ... |
| 353 | | |
| 354 | | def some_method(self): |
| 355 | | print datetime.datetime.now() |
| 356 | | cursor = connection.cursor() |
| 357 | | cursor.execute("UPDATE something;") |
| 358 | | }}} |
| 359 | | |
| 360 | | == Moved "auth" and "core" models to django.contrib == |
| 361 | | |
| 362 | | '''Status: Done''' |
| 363 | | |
| 364 | | See http://groups.google.com/group/django-developers/browse_thread/thread/276d071a74543448/7d4b1c40c2d53393 |
| 365 | | |
| 366 | | * Old: {{{django.models.auth}}} |
| 367 | | * New: {{{django.contrib.auth.models}}} |
| 368 | | |
| 369 | | * Old: {{{django.models.core.sites}}} |
| 370 | | * New: {{{django.contrib.sites.models}}} |
| 371 | | |
| 372 | | * Old: {{{django.models.core.contenttypes}}} |
| 373 | | * New: {{{django.contrib.contenttypes.models}}} |
| 374 | | |
| 375 | | * Old: {{{django.models.core.packages}}} |
| 376 | | * New: {{{django.contrib.contenttypes.models}}} ("Packages" will most likely be removed in the future.) |
| 377 | | |
| 378 | | == Moved Session model and middleware from core to django.contrib == |
| 379 | | |
| 380 | | '''Status: Done''' |
| 381 | | |
| 382 | | The location of the session middleware has changed. |
| 383 | | |
| 384 | | * Old: {{{django.middleware.sessions.SessionMiddleware}}} |
| 385 | | * New: {{{django.contrib.sessions.middleware.SessionMiddleware}}} |
| 386 | | |
| 387 | | Make sure to update your {{{MIDDLEWARE_CLASSES}}} setting, if you're using sessions. |
| 388 | | |
| 389 | | Also, the {{{Session}}} model has moved from django/models/core.py to django/contrib/sessions/models.py. If you're accessing the {{{Session}}} model for some reason, note that location change. |
| 390 | | |
| 391 | | == Changed the parameters you pass to generic views == |
| 392 | | |
| 393 | | '''Status: Done''' |
| 394 | | |
| 395 | | Because there's no longer a concept of {{{module_name}}}, the "info_dicts" passed to [http://www.djangoproject.com/documentation/generic_views/ generic views] no longer accept {{{"app_label"}}} and {{{"module_name"}}}. Instead, pass the parameter {{{"model"}}}, which should be your model class. |
| 396 | | |
| 397 | | These examples assume models live in {{{myproject/blog/models.py}}}. |
| 398 | | |
| 399 | | Old: |
| 400 | | {{{ |
| 401 | | #!python |
| 402 | | info_dict = { |
| 403 | | 'app_label': 'blog', |
| 404 | | 'module_name': 'entries' |
| 405 | | } |
| 406 | | }}} |
| 407 | | |
| 408 | | New: |
| 409 | | {{{ |
| 410 | | #!python |
| 411 | | from myproject.blog.models import Entry |
| 412 | | info_dict = { |
| 413 | | 'model': Entry |
| 414 | | } |
| 415 | | }}} |
| 416 | | |
| 417 | | == Changed template names in generic views == |
| 418 | | |
| 419 | | '''Status: Done''' |
| 420 | | |
| 421 | | Because there's no longer a concept of {{{module_name}}}, [http://www.djangoproject.com/documentation/generic_views/ generic views] no longer create templates based on the {{{module_name}}}. Wherever they used {{{module_name}}}, they now use {{{model_name}}}, a lowercase version of the model name. |
| 422 | | |
| 423 | | Note that {{{app_label}}} remains the same. |
| 424 | | |
| 425 | | These examples assume models live in {{{myproject/blog/models.py}}}. |
| 426 | | |
| 427 | | * Old: {{{blog/entries_archive.html}}} |
| 428 | | * New: {{{blog/entry_archive.html}}} |
| 429 | | |
| 430 | | == Moved admin URLconf to shorten its path == |
| 431 | | |
| 432 | | '''Status: Done''' |
| 433 | | |
| 434 | | * Old: {{{django.contrib.admin.urls.admin}}} |
| 435 | | * New: {{{django.contrib.admin.urls}}} |
| 436 | | |
| 437 | | == Moved settings into an instance == |
| 438 | | |
| 439 | | '''Status: Done''' |
| 440 | | |
| 441 | | To make it easier to switch settings in situations where you would need multiple different settings - for example when trying to use multiple django projects within one server context or when using Django apps within a bigger WSGI scenario - the settings were moved out of a dedicated module {{{django.conf.settings}}} into an instance in the {{{django.conf}}} module. So now you need to import the {{{settings}}} object and reference settings as attributes of that instance. |
| 442 | | |
| 443 | | Wrappers around the Django machinery can make use of this by exchanging the settings instance with a proxy instance that delegates attribute access to a per-thread or per-location global. |
| 444 | | |
| 445 | | * Old: {{{from django.conf.settings import LANGUAGE_CODE}}} |
| 446 | | * New: {{{from django.conf import settings}}} |
| 447 | | |
| 448 | | == Renamed core database tables == |
| 449 | | |
| 450 | | '''Status: Done''' |
| 451 | | |
| 452 | | Renamed a bunch of the code Django tables. To upgrade, execute this SQL in your database: |
| | 13 | == Database changes you'll need to make == |
| | 14 | |
| | 15 | === Rename core database tables === |
| | 16 | |
| | 17 | We've renamed a bunch of the code Django tables. To upgrade, execute this SQL in your database: |
| | 81 | === Changes to model syntax === |
| | 82 | |
| | 83 | * {{{class META}}} should now be {{{class Meta}}}. The latter is easier on the eyes. |
| | 84 | |
| | 85 | * {{{module_name}}} is no longer a valid parameter to {{{class Meta}}}. |
| | 86 | |
| | 87 | === Moved admin options to 'class Admin' === |
| | 88 | |
| | 89 | Instead of {{{admin=meta.Admin}}} in the {{{class META}}}, all admin options are in an inner {{{class Admin}}}. |
| | 90 | |
| | 91 | Old: |
| | 92 | {{{ |
| | 93 | #!python |
| | 94 | class Person(meta.Model): |
| | 95 | first_name = meta.CharField(maxlength=30) |
| | 96 | last_name = meta.CharField(maxlength=30) |
| | 97 | class META: |
| | 98 | admin = meta.Admin( |
| | 99 | list_display = ('first_name', 'last_name') |
| | 100 | ) |
| | 101 | }}} |
| | 102 | |
| | 103 | New: |
| | 104 | {{{ |
| | 105 | #!python |
| | 106 | class Person(models.Model): |
| | 107 | first_name = models.CharField(maxlength=30) |
| | 108 | last_name = models.CharField(maxlength=30) |
| | 109 | class Admin: |
| | 110 | list_display = ('first_name', 'last_name') |
| | 111 | }}} |
| | 112 | |
| | 113 | === Model methods no longer automatically have access to datetime and db modules === |
| | 114 | |
| | 115 | Formerly, each model method magically had access to the {{{datetime}}} module and to the variable {{{db}}}, which represents the current database connection. Now, those have to be imported explicitly. |
| | 116 | |
| | 117 | Old: |
| | 118 | {{{ |
| | 119 | #!python |
| | 120 | def some_method(self): |
| | 121 | print datetime.datetime.now() |
| | 122 | cursor = db.cursor() |
| | 123 | cursor.execute("UPDATE something;") |
| | 124 | }}} |
| | 125 | |
| | 126 | New: |
| | 127 | {{{ |
| | 128 | #!python |
| | 129 | import datetime |
| | 130 | from django.db import connection |
| | 131 | |
| | 132 | # ... |
| | 133 | |
| | 134 | def some_method(self): |
| | 135 | print datetime.datetime.now() |
| | 136 | cursor = connection.cursor() |
| | 137 | cursor.execute("UPDATE something;") |
| | 138 | }}} |
| | 139 | |
| | 140 | === Access table-level DB API functions via model classes, not with magic modules === |
| | 141 | |
| | 142 | All "table-level" functions -- ways of retrieving records tablewide rather than performing instance-specific tasks -- are now accessed via a model class's {{{objects}}} attribute. They aren't direct methods of a model instance object because we want to keep the "table-wide" and "row-specific" namespaces separate. |
| | 143 | |
| | 144 | {{{ |
| | 145 | #!python |
| | 146 | from myproject.people.models import Person |
| | 147 | p_list = Person.objects.get_list() |
| | 148 | p = Person.objects.get_object() |
| | 149 | }}} |
| | 150 | |
| | 151 | This doesn't work from an instance. |
| | 152 | |
| | 153 | {{{ |
| | 154 | #!python |
| | 155 | p = Person.objects.get_object(pk=1) |
| | 156 | p.objects.get_list() # Raises AttributeError |
| | 157 | }}} |
| | 158 | |
| | 159 | === Override default manager name ("objects") === |
| | 160 | |
| | 161 | If a model already has an {{{objects}}} attribute, you'll need to specify an alternate name for the magic {{{objects}}}. |
| | 162 | |
| | 163 | {{{ |
| | 164 | #!python |
| | 165 | class Person(models.Model): |
| | 166 | first_name = models.CharField(maxlength=30) |
| | 167 | last_name = models.CharField(maxlength=30) |
| | 168 | objects = models.TextField() |
| | 169 | people = models.Manager() |
| | 170 | |
| | 171 | p = Person(first_name='Mary', last_name='Jones', objects='Hello there.') |
| | 172 | p.save() |
| | 173 | p.objects == 'Hello there.' |
| | 174 | Person.people.get_list() |
| | 175 | }}} |
| | 176 | |
| | 177 | === Custom managers, and multiple managers === |
| | 178 | |
| | 179 | You can create as many managers as you want. When necessary (such as on the admin), Django will use the first one defined, in order. |
| | 180 | |
| | 181 | If you define at least one custom manager, it will not get the default "objects" manager. |
| | 182 | |
| | 183 | {{{ |
| | 184 | #!python |
| | 185 | class Person(models.Model): |
| | 186 | first_name = models.CharField(maxlength=30) |
| | 187 | last_name = models.CharField(maxlength=30) |
| | 188 | people = models.Manager() |
| | 189 | fun_people = SomeOtherManager() |
| | 190 | }}} |
| | 191 | |
| | 192 | === Added a more powerful way of overriding model methods, removed hard-coded _pre_save(), _post_save(), etc. === |
| | 193 | |
| | 194 | Proper subclassing of methods now works, so you can subclass the automatic {{{save()}}} and {{{delete()}}} methods. This removes the need for the {{{_pre_save()}}}, {{{_post_save()}}}, {{{_pre_delete()}}} and {{{_post_delete()}}} hooks -- all of which have been removed. Example: |
| | 195 | |
| | 196 | {{{ |
| | 197 | #!python |
| | 198 | class Person(models.Model): |
| | 199 | first_name = models.CharField(maxlength=30) |
| | 200 | last_name = models.CharField(maxlength=30) |
| | 201 | |
| | 202 | def save(self): |
| | 203 | self.do_something() |
| | 204 | super(Person, self).save() # Call the "real" save() method. |
| | 205 | self.do_something_else() |
| | 206 | }}} |
| | 207 | |
| | 208 | You can even skip saving (as requested in #1014). |
| | 209 | |
| | 210 | {{{ |
| | 211 | #!python |
| | 212 | class Person(models.Model): |
| | 213 | first_name = models.CharField(maxlength=30) |
| | 214 | last_name = models.CharField(maxlength=30) |
| | 215 | |
| | 216 | def save(self): |
| | 217 | if datetime.date.today() > datetime.date(2005, 1, 1): |
| | 218 | super(Person, self).save() # Call the "real" save() method. |
| | 219 | else: |
| | 220 | # Don't save. |
| | 221 | pass |
| | 222 | }}} |
| | 223 | |
| | 224 | === Database connection relocated/renamed === |
| | 225 | |
| | 226 | For any code that uses the raw database connection, use {{{django.db.connection}}} instead of {{{django.core.db.db}}}. |
| | 227 | |
| | 228 | Old: |
| | 229 | {{{ |
| | 230 | #!python |
| | 231 | from django.core.db import db |
| | 232 | cursor = db.cursor() |
| | 233 | }}} |
| | 234 | |
| | 235 | New: |
| | 236 | {{{ |
| | 237 | #!python |
| | 238 | from django.db import connection |
| | 239 | cursor = connection.cursor() |
| | 240 | }}} |
| | 241 | |
| | 242 | Backend-specific functions, if you should need them, are available at {{{django.db.backend}}}. |
| | 243 | |
| | 244 | Old: |
| | 245 | {{{ |
| | 246 | #!python |
| | 247 | from django.core import db |
| | 248 | db.quote_name('foo') |
| | 249 | }}} |
| | 250 | |
| | 251 | New: |
| | 252 | {{{ |
| | 253 | #!python |
| | 254 | from django.db import backend |
| | 255 | backend.quote_name('foo') |
| | 256 | }}} |
| | 257 | |
| | 258 | Also, the various backend functionality has been split into three separate modules for each backend -- {{{base.py}}}, {{{creation.py}}} and {{{introspection.py}}}. This is purely for performance and memory savings, so that basic, everyday Django usage doesn't have to load the introspective functionality into memory. |
| | 259 | |
| | 260 | === Renamed !DoesNotExist exception === |
| | 261 | |
| | 262 | Instead of {{{people.PersonDoesNotExist}}}, it's {{{Person.DoesNotExist}}}. |
| | 263 | |
| | 264 | Old: |
| | 265 | {{{ |
| | 266 | #!python |
| | 267 | from django.models.myapp import people |
| | 268 | try: |
| | 269 | people.get_object(pk=1) |
| | 270 | except people.PersonDoesNotExist: |
| | 271 | print "Not there" |
| | 272 | }}} |
| | 273 | |
| | 274 | New: |
| | 275 | {{{ |
| | 276 | #!python |
| | 277 | from path.to.myapp.models import Person |
| | 278 | try: |
| | 279 | Person.objects.get_object(pk=1) |
| | 280 | except Person.DoesNotExist: |
| | 281 | print "Not there" |
| | 282 | }}} |
| | 283 | |
| | 284 | === Moved admin URLconf to shorten its path === |
| | 285 | |
| | 286 | You'll need to change your URLconf to {{{include}}} the new location. |
| | 287 | |
| | 288 | * Old: {{{django.contrib.admin.urls.admin}}} |
| | 289 | * New: {{{django.contrib.admin.urls}}} |
| | 290 | |
| | 291 | === get_object_or_404 and get_list_or_404 now take model classes, not modules === |
| | 292 | |
| | 293 | Old: |
| | 294 | {{{ |
| | 295 | #!python |
| | 296 | get_object_or_404(polls, pk=1) |
| | 297 | }}} |
| | 298 | |
| | 299 | New: |
| | 300 | {{{ |
| | 301 | #!python |
| | 302 | get_object_or_404(Poll, pk=1) |
| | 303 | }}} |
| | 304 | |
| | 305 | === Moved "auth" and "core" models to django.contrib === |
| | 306 | |
| | 307 | See http://groups.google.com/group/django-developers/browse_thread/thread/276d071a74543448/7d4b1c40c2d53393 |
| | 308 | |
| | 309 | * Old: {{{django.models.auth}}} |
| | 310 | * New: {{{django.contrib.auth.models}}} |
| | 311 | |
| | 312 | * Old: {{{django.models.core.sites}}} |
| | 313 | * New: {{{django.contrib.sites.models}}} |
| | 314 | |
| | 315 | * Old: {{{django.models.core.contenttypes}}} |
| | 316 | * New: {{{django.contrib.contenttypes.models}}} |
| | 317 | |
| | 318 | * Old: {{{django.models.core.packages}}} |
| | 319 | * New: {{{django.contrib.contenttypes.models}}} ("Packages" will most likely be removed in the future.) |
| | 320 | |
| | 321 | === Moved Session model and middleware from core to django.contrib === |
| | 322 | |
| | 323 | The location of the session middleware has changed. |
| | 324 | |
| | 325 | * Old: {{{django.middleware.sessions.SessionMiddleware}}} |
| | 326 | * New: {{{django.contrib.sessions.middleware.SessionMiddleware}}} |
| | 327 | |
| | 328 | Make sure to update your {{{MIDDLEWARE_CLASSES}}} setting, if you're using sessions. |
| | 329 | |
| | 330 | Also, the {{{Session}}} model has moved from django/models/core.py to django/contrib/sessions/models.py. If you're accessing the {{{Session}}} model for some reason, note that location change. |
| | 331 | |
| | 332 | === Changed the parameters you pass to generic views === |
| | 333 | |
| | 334 | Because there's no longer a concept of {{{module_name}}}, the "info_dicts" passed to [http://www.djangoproject.com/documentation/generic_views/ generic views] no longer accept {{{"app_label"}}} and {{{"module_name"}}}. Instead, pass the parameter {{{"model"}}}, which should be your model class. |
| | 335 | |
| | 336 | These examples assume models live in {{{myproject/blog/models.py}}}. |
| | 337 | |
| | 338 | Old: |
| | 339 | {{{ |
| | 340 | #!python |
| | 341 | info_dict = { |
| | 342 | 'app_label': 'blog', |
| | 343 | 'module_name': 'entries' |
| | 344 | } |
| | 345 | }}} |
| | 346 | |
| | 347 | New: |
| | 348 | {{{ |
| | 349 | #!python |
| | 350 | from myproject.blog.models import Entry |
| | 351 | info_dict = { |
| | 352 | 'model': Entry |
| | 353 | } |
| | 354 | }}} |
| | 355 | |
| | 356 | === Changed template names in generic views === |
| | 357 | |
| | 358 | Because there's no longer a concept of {{{module_name}}}, [http://www.djangoproject.com/documentation/generic_views/ generic views] no longer create templates based on the {{{module_name}}}. Wherever they used {{{module_name}}}, they now use {{{model_name}}}, a lowercase version of the model name. |
| | 359 | |
| | 360 | Note that {{{app_label}}} remains the same. |
| | 361 | |
| | 362 | These examples assume models live in {{{myproject/blog/models.py}}}. |
| | 363 | |
| | 364 | * Old: {{{blog/entries_archive.html}}} |
| | 365 | * New: {{{blog/entry_archive.html}}} |
| | 366 | |
| | 367 | === Moved settings into an instance === |
| | 368 | |
| | 369 | To make it easier to switch settings in situations where you would need multiple different settings - for example when trying to use multiple django projects within one server context or when using Django apps within a bigger WSGI scenario - the settings were moved out of a dedicated module {{{django.conf.settings}}} into an instance in the {{{django.conf}}} module. So now you need to import the {{{settings}}} object and reference settings as attributes of that instance. |
| | 370 | |
| | 371 | Wrappers around the Django machinery can make use of this by exchanging the settings instance with a proxy instance that delegates attribute access to a per-thread or per-location global. |
| | 372 | |
| | 373 | * Old: {{{from django.conf.settings import LANGUAGE_CODE}}} |
| | 374 | * New: {{{from django.conf import settings}}} |
| | 375 | |
| | 376 | === Removed !SilentVariableFailure exception === |
| | 377 | |
| | 378 | Old behavior: Any exception that subclasses {{{django.core.template.SilentVariableFailure}}} fails silently in the template system. |
| | 379 | |
| | 380 | New behavior: Any exception that has a {{{silent_variable_failure}}} attribute fails silently in the template system. {{{django.core.template.SilentVariableFailure}}} no longer exists. |
| | 381 | |
| | 382 | |
| | 383 | |
| | 384 | |
| | 385 | |
| | 386 | == New functionality you can start using == |
| | 387 | |
| | 388 | === Models support properties === |
| | 389 | |
| | 390 | Unlike before, properties are supported on models. |
| | 391 | |
| | 392 | {{{ |
| | 393 | #!python |
| | 394 | from django.db import models |
| | 395 | |
| | 396 | class Person(models.Model): |
| | 397 | first_name = models.CharField(maxlength=30) |
| | 398 | last_name = models.CharField(maxlength=30) |
| | 399 | |
| | 400 | def _get_full_name(self): |
| | 401 | return "%s %s" % (self.first_name, self.last_name) |
| | 402 | full_name = property(_get_full_name) |
| | 403 | }}} |
| | 404 | |
| | 405 | === You can override table-level functions === |
| | 406 | |
| | 407 | You can override any table-level functions, such as {{{get_list()}}} or {{{get_object()}}}. Do this by creating a custom {{{models.Manager}}} subclass and passing it to your model. |
| | 408 | |
| | 409 | {{{ |
| | 410 | #!python |
| | 411 | from django.db import models |
| | 412 | class PersonManager(models.Manager): |
| | 413 | def get_list(self, **kwargs): |
| | 414 | # Changes get_list() to hard-code a limit=10. |
| | 415 | kwargs['limit'] = 10 |
| | 416 | return models.Manager.get_list(self, **kwargs) # Call the "real" get_list() method. |
| | 417 | |
| | 418 | class Person(models.Model): |
| | 419 | first_name = models.CharField(maxlength=30) |
| | 420 | last_name = models.CharField(maxlength=30) |
| | 421 | objects = PersonManager() |
| | 422 | }}} |
| | 423 | |
| | 424 | If a manager needs to access its associated class, it should use {{{self.klass}}}. Example: |
| | 425 | |
| | 426 | {{{ |
| | 427 | #!python |
| | 428 | class PersonManager(models.Manager): |
| | 429 | def get_fun_person(self): |
| | 430 | try: |
| | 431 | return self.get_object(fun__exact=True) |
| | 432 | except self.klass.DoesNotExist: |
| | 433 | print "Doesn't exist." |
| | 434 | }}} |
| | 435 | |
| | 436 | |
| | 437 | |
| | 438 | |
| | 439 | |
| | 440 | |
| | 441 | == Stuff that still needs to be done == |
| | 442 | |
| | 443 | === Automatic manipulators === |
| | 444 | |
| | 445 | '''Status: Mostly done, with some quirks left''' |
| | 446 | |
| | 447 | Old: |
| | 448 | {{{ |
| | 449 | #!python |
| | 450 | from django.models.myapp import people |
| | 451 | m1 = people.AddManipulator() |
| | 452 | m2 = people.ChangeManipulator(3) |
| | 453 | }}} |
| | 454 | |
| | 455 | New: |
| | 456 | {{{ |
| | 457 | #!python |
| | 458 | from path.to.myapp.models import Person |
| | 459 | m1 = Person.AddManipulator() |
| | 460 | m2 = Person.ChangeManipulator(3) |
| | 461 | }}} |
| | 462 | |