class Model(**kwargs)

The keyword arguments are the names of the fields you’ve defined on your model. Note that instantiating a model in no way touches your database; for that, you need to save().

Model의 __init__을 이용해 Model이 생성될 때 어떤작업을 하고 싶은 경우 __init__을 override하는 것보다 . 아래와 같이 class method를 이용해 처리할것을 추천한다. 

1. Add a classmethod on the model class:

from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=100)

    @classmethod
    def create(cls, title):
        book = cls(title=title)
        # do something with the book
        return book

book = Book.create("Pride and Prejudice")

2. Add a method on a custom manager (usually preferred):

class BookManager(models.Manager):
    def create_book(self, title):
        book = self.create(title=title)
        # do something with the book
        return book

class Book(models.Model):
    title = models.CharField(max_length=100)

    objects = BookManager()

book = Book.objects.create_book("Pride and Prejudice")

Customizing model loading

Model이 loading될때 추가 작업을 하고싶은 경우 from_db()를 override한다.classmethod

Model.from_db(db, field_names, values)

참고자료) https://www.webforefront.com/django/modelmethodsandmetaoptions.html 하단부

Refreshing objects from database

If you delete a field from a model instance, accessing it again reloads the value from the database:

>>> obj = MyModel.objects.first()
>>> del obj.field
>>> obj.field  # Loads the field from the database

Model.refresh_from_db(using=None, fields=None)

if you need to reload a model’s values from the database, you can use the refresh_from_db() method.

Validating objects

There are three steps involved in validating a model:

  1. Validate the model fields – Model.clean_fields()
  2. Validate the model as a whole – Model.clean()
  3. Validate the field uniqueness – Model.validate_unique()

All three steps are performed when you call a model’s full_clean() method.


Model.full_clean(exclude=None, validate_unique=True)

This method calls Model.clean_fields(), Model.clean(), and Model.validate_unique() (if validate_unique is True) 유효성 검사중에 어디서든 ValidationError raise되면 유효성검사가 실패한것이 된다.

The optional exclude argument can be used to provide a list of field names that can be excluded from validation and cleaning. ModelForm uses this argument to exclude fields that aren’t present on your form from being validated since any errors raised could not be corrected by the user.

Note that full_clean() will not be called automatically when you call your model’s save() method. 

full_clean()의 호출방법의 예는 아래와 같다.

from django.core.exceptions import ValidationError
try:
    article.full_clean()
except ValidationError as e:
    # Do something based on the errors contained in e.message_dict.
    # Display them to a user, or handle them programmatically.
    pass


Model.clean_fields(exclude=None)

This method will validate all fields on your model. The optional exclude argument lets you provide a list of field names to exclude from validation. It will raise a ValidationError if any fields fail validation.

The second step full_clean() performs is to call Model.clean(). This method should be overridden to perform custom validation on your model.

Model.clean()

This method should be used to provide custom model validation, and to modify attributes on your model if desired. For instance, you could use it to automatically provide a value for a field, or to do validation that requires access to more than a single field: 아래와 같이 여러 항목을 이용해서 validate하는 경우 clean()에서 작업한다.

import datetime
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _

class Article(models.Model):
    ...
    def clean(self):
        # Don't allow draft entries to have a pub_date.
        if self.status == 'draft' and self.pub_date is not None:
            raise ValidationError(_('Draft entries may not have a publication date.'))
        # Set the pub_date for published items if it hasn't been set already.
        if self.status == 'published' and self.pub_date is None:
            self.pub_date = datetime.date.today()

To assign exceptions to a specific field, instantiate the ValidationError with a dictionary, where the keys are the field names. 예를 들어 pub_date field의 문제의 경우 아래와 같이 해준다.

class Article(models.Model):
    ...
    def clean(self):
        # Don't allow draft entries to have a pub_date.
        if self.status == 'draft' and self.pub_date is not None:
            raise ValidationError({'pub_date': _('Draft entries may not have a publication date.')})

Model.clean()에서 여러 에러가 발생하는 경우 아래와 같이  you can also pass a dictionary mapping field names to errors:

raise ValidationError({
    'title': ValidationError(_('Missing title.'), code='required'),
    'pub_date': ValidationError(_('Invalid date.'), code='invalid'),
})

ModelForm에서 Meta.fields or Meta.exclude를 이용해 제외된 field의 ValidationError를 raise해도작동이 되지 않고 ValueError가 발생하게 되는데 이에 대한 우회방법은 아래와 같다. 

To work around this dilemma, instead override Model.clean_fields() as it receives the list of fields that are excluded from validation. For example:

class Article(models.Model):
    ...
    def clean_fields(self, exclude=None): #마치 제외된 field가 없는척한다.
        super().clean_fields(exclude=exclude)
        if self.status == 'draft' and self.pub_date is not None:
            if exclude and 'status' in exclude:
                raise ValidationError(
                    _('Draft entries may not have a publication date.')
                )
            else:
                raise ValidationError({
                    'status': _(
                        'Set status to draft if there is not a '
                        'publication date.'
                     ),
                })

Model.validate_unique(exclude=None)

model의 field중에 unique, unique_together ,  unique_for_date|month|year 이 설정되있는 경우 호출되며 어떻게 unique를 결정할지에 대한 내용이 들어 간다.



Saving objects

To save an object back to the database, call save():

Model.save(force_insert=False, force_update=False,using=DEFAULT_DB_ALIAS, update_fields=None)



Auto-incrementing primary keys

AutoField — an auto-incrementing primary key — then that auto-incremented value will be calculated and saved as an attribute on your object the first time you call save():

>>> b2 = Blog(name='Cheddar Talk', tagline='Thoughts on cheese.')
>>> b2.id     # Returns None, because b2 doesn't have an ID yet.
>>> b2.save()
>>> b2.id     # Returns the ID of your new object.

There’s no way to tell what the value of an ID will be before you call save()

For convenience, each model has an AutoField named id by default unless you explicitly specify primary_key=True on a field in your model. See the documentation for AutoField for more details. 특별히 특정 field에  primary_key=True 를 설정하지 않으면 id라는 AutoField 가 만들어 지게 된다.



Model.pk

자동으로 만들어진 AutoField이건 개발자가 primary_key=True 설정해 지정된 field이건 간에 간단히 pk 이름으로 접근할수 있다.



AutoField 로 정의 되어있더라도 개발자가 임의로 값을 할당할수 있다.

AutoField 이더라도 아래와 같이 처음 obj가 생성될때 넣어주면 이미 있지 않은 id라면 만들어진다. 이미 있던 id라면 내용이 update가 된다.

>>> b3 = Blog(id=3, name='Cheddar Talk', tagline='Thoughts on cheese.')
>>> b3.id     # Returns 3.
>>> b3.save()
>>> b3.id     # Returns 3.
b4 = Blog(id=3, name='Not Cheddar', tagline='Anything but cheese.')
b4.save()  # Overrides the previous blog with ID=3!

form validation doc https://docs.djangoproject.com/en/3.0/ref/forms/validation/

참고 블로그) 내용이 official doc보다 간결하다. http://www.deekras.com/django-validations-during-form-processing.html

These are normally executed when you call the is_valid() method on a form. There are other things that can also trigger cleaning and validation (accessing the errors attribute or calling full_clean() directly), but normally they won’t be needed. 

(form validation은 form의 is_valid()를 통해 수행된다. form에 있는 errors에 접근하거나 full_clean()을 통해 수행되기도 한다. full_clean()는 model validation에서 사용되는 method이름과 같다.)

  •  to_python() 스트링을 python data type으로 전환한다.
  • validate() method on a Field handles field-specific validation that is not suitable for a validator. 
  • run_validators() method on a Field runs all of the field’s validators and aggregates all the errors into a single ValidationError. You shouldn’t need to override this method.
  • clean() method on a Field subclass is responsible for running to_python(), validate(), and run_validators() in the correct order and propagating their errors. This method returns the clean data, which is then inserted into the cleaned_data dictionary of the form.
  • clean_<fieldname>() method is called on a form subclass. This method does any cleaning that is specific to that particular attribute, unrelated to the type of field that it is. This method is not passed any parameters. You will need to look up the value of the field in self.cleaned_data and remember that it will be a Python object at this point, not the original string submitted in the form (it will be in cleaned_data because the general field clean() method, above, has already cleaned the data once).

기본적으로 위와 같은 순서로 진행되며 각 단계에서 무제가 발생하면 ValidationError를 raise한다.

Raising ValidationError

# Good
ValidationError(_('Invalid value'), code='invalid')

# Bad
ValidationError(_('Invalid value'))

# Good
ValidationError(
    _('Invalid value: %(value)s'),
    params={'value': '42'},
)

# Bad
ValidationError(_('Invalid value: %s') % value)

# Good
ValidationError(
    _('Invalid value: %(value)s'),
    params={'value': '42'},
)

# Bad
ValidationError(
    _('Invalid value: %s'),
    params=('42',),
)

Wrap the message with gettext to enable translation: _() 를 말한다.

# Good
ValidationError(_('Invalid value'))

# Bad
ValidationError('Invalid value')

Putting it all together:

raise ValidationError(
    _('Invalid value: %(value)s'),
    code='invalid',
    params={'value': '42'},)

Raising multiple errors

# Good
raise ValidationError([
    ValidationError(_('Error 1'), code='error1'),
    ValidationError(_('Error 2'), code='error2'),
])

# Bad
raise ValidationError([
    _('Error 1'),
    _('Error 2'),
])

기존 field를 extends해서 새로운 field를 만들고 새로운 validation rule을 지정하는 방법

1방법)

from django.core import validators
from django.forms import CharField

class SlugField(CharField):
    default_validators = [validators.validate_slug]
slug = forms.SlugField()

2방법)

slug = forms.CharField(validators=[validators.validate_slug])

validator만드는 방법 https://docs.djangoproject.com/en/3.0/ref/validators/

form의 field에 validation을 customize하는 방법 (to_python , validate)

from django import forms
from django.core.validators import validate_email

class MultiEmailField(forms.Field):
    def to_python(self, value):
        """Normalize data to a list of strings."""
        # Return an empty list if no input was given.
        if not value:
            return []
        return value.split(',')

    def validate(self, value):
        """Check if value consists only of valid emails."""
        # Use the parent's handling of required fields, etc.
        super().validate(value)
        for email in value:
            validate_email(email)
class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()
    recipients = MultiEmailField()
    cc_myself = forms.BooleanField(required=False)

Use MultiEmailField like any other form field. When the is_valid() method is called on the form, the MultiEmailField.clean() method will be run as part of the cleaning process and it will, in turn, call the custom to_python() and validate() methods.

form 단위에서 field validation 을 customize 하는 경우

from django import forms

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean_recipients(self):
        data = self.cleaned_data['recipients']
        if "fred@example.com" not in data:
            raise forms.ValidationError("You have forgotten about Fred!")

        # Always return a value to use as the new cleaned data, even if
        # this method didn't change it.
        return data

이미 fileld단위의 validation이 끝났기 때문에 cleaned_data에 접근 가능하다.

Cleaning and validating fields that depend on each other (form 단위에서 여러개의 fields을 이용해서 validation하는 경우)

from django import forms

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = super().clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject:
            # Only do something if both fields are valid so far.
            if "help" not in subject:
                raise forms.ValidationError(
                    "Did not send for 'help' in the subject despite "
                    "CC'ing yourself."
                )

In this code, if the validation error is raised, the form will display an error message at the top of the form (normally) describing the problem.

The call to super().clean() in the example code ensures that any validation logic in parent classes is maintained. If your form inherits another that doesn’t return a cleaned_data dictionary in its clean() method (doing so is optional), then don’t assign cleaned_data to the result of the super() call and use self.cleaned_data instead:

def clean(self):
    super().clean()
    cc_myself = self.cleaned_data.get("cc_myself")
    ...
from django import forms
class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = super().clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject and "help" not in subject:
            msg = "Must put 'help' in subject when cc'ing yourself."
            self.add_error('cc_myself', msg)
            self.add_error('subject', msg)

original source : https://simpleisbetterthancomplex.com/tutorial/2016/08/03/how-to-paginate-with-django.html

bootstrap 

https://hackerthemes.com/bootstrap-cheatsheet/#page-item__active

아래 블로그 실제 코드에서 bootstrap convention이 조금 빠진게 있으므로 주의할것. 

django에서 restful 형태인 /something/page/1 이런 형태로 구현하기 조금 까다롭다. get 방식으로 /something/page/?page=1이런형태로 구현하는 것이 조금 편하고 get query string으로 전달된 값은 request.POST 로 접근 되어도 request.GET을 통해 접근할수 있다. 예를 들어 /something/page/?page=1로 접근해서 해당페이지에 있는 폼을 작성해서 다시 자기 자신페이지로 오는 경우 post 방법이지만 get query string정보도 가지고 있다.

이내용을 보면 좀더 알수 있다. https://simpleisbetterthancomplex.com/snippet/2016/08/22/dealing-with-querystring-parameters.html

The Paginator

The paginator classes lives in django.core.paginator. We will be working mostly with the Paginator and Page classes.

Consider the auth.User table has 53 user instances.

from django.contrib.auth.models import User
from django.core.paginator import Paginator

user_list = User.objects.all()
paginator = Paginator(user_list, 10)

In the example above I’m telling Paginator to paginate the user_list QuerySet in pages of 10. This will create a 6 pages result. The first 5 pages with 10 users each and the last page with 3 users.

image

The Paginator.page() method will return a given page of the paginated results, which is an instance of Page. This is what we will return to the template.

users = paginator.page(2)
image

The Page.next_page_number() and Page.previous_page_number() methods raises InvalidPage if next/previous page doesn’t exist.

The Page.start_index() and Page.end_index() are relative to the page number.

>>> users = paginator.page(6)  # last page
<Page 6 of 6>
>>> users.start_index()
51
>>> users.end_index()
53

The process is basically done by querying the database, then pass the QuerySet to the Paginator, grab a Page and return to the template. The rest is done in the template.

Let’s see now some practical examples.

Pagination with Function-Based Views

views.py

from django.contrib.auth.models import User
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

def index(request):
    user_list = User.objects.all()
    page = request.GET.get('page', 1)

    paginator = Paginator(user_list, 10)
    try:
        users = paginator.page(page)
    except PageNotAnInteger:
        users = paginator.page(1)
    except EmptyPage:
        users = paginator.page(paginator.num_pages)

    return render(request, 'core/user_list.html', { 'users': users })

user_list.html

<table class="table table-bordered">
  <thead>
    <tr>
      <th>Username</th>
      <th>First name</th>
      <th>Email</th>
    </tr>
  </thead>
  <tbody>
    {% for user in users %}
      <tr>
        <td>{{ user.username }}</td>
        <td>{{ user.first_name }}</td>
        <td>{{ user.email }}</td>
      </tr>
    {% endfor %}
  </tbody>
</table>

{% if users.has_other_pages %}
  <ul class="pagination">
    {% if users.has_previous %}
      <li><a href="?page={{ users.previous_page_number }}">&laquo;</a></li>
    {% else %}
      <li class="disabled"><span>&laquo;</span></li>
    {% endif %}
    {% for i in users.paginator.page_range %}
      {% if users.number == i %}
        <li class="active"><span>{{ i }} <span class="sr-only">(current)</span></span></li>
      {% else %}
        <li><a href="?page={{ i }}">{{ i }}</a></li>
      {% endif %}
    {% endfor %}
    {% if users.has_next %}
      <li><a href="?page={{ users.next_page_number }}">&raquo;</a></li>
    {% else %}
      <li class="disabled"><span>&raquo;</span></li>
    {% endif %}
  </ul>
{% endif %}

The result is something like this:

The example above is using Bootstrap 3.

Pagination with Class-Based Views

views.py

class UserListView(ListView):
    model = User
    template_name = 'core/user_list.html'  # Default: <app_label>/<model_name>_list.html
    context_object_name = 'users'  # Default: object_list
    paginate_by = 10
    queryset = User.objects.all()  # Default: Model.objects.all()

user_list.html

<table class="table table-bordered">
  <thead>
    <tr>
      <th>Username</th>
      <th>First name</th>
      <th>Email</th>
    </tr>
  </thead>
  <tbody>
    {% for user in users %}
      <tr>
        <td>{{ user.username }}</td>
        <td>{{ user.first_name }}</td>
        <td>{{ user.email }}</td>
      </tr>
    {% endfor %}
  </tbody>
</table>

{% if is_paginated %}
  <ul class="pagination">
    {% if page_obj.has_previous %}
      <li><a href="?page={{ page_obj.previous_page_number }}">&laquo;</a></li>
    {% else %}
      <li class="disabled"><span>&laquo;</span></li>
    {% endif %}
    {% for i in paginator.page_range %}
      {% if page_obj.number == i %}
        <li class="active"><span>{{ i }} <span class="sr-only">(current)</span></span></li>
      {% else %}
        <li><a href="?page={{ i }}">{{ i }}</a></li>
      {% endif %}
    {% endfor %}
    {% if page_obj.has_next %}
      <li><a href="?page={{ page_obj.next_page_number }}">&raquo;</a></li>
    {% else %}
      <li class="disabled"><span>&raquo;</span></li>
    {% endif %}
  </ul>
{% endif %}

설명 블로그

modelformset changing query set 

modelformset은 기본으로 이미 입력된 모든 데이터를 보여준다. 이를 바꿀때 사용

https://docs.djangoproject.com/en/3.0/topics/forms/modelforms/#changing-the-queryset

formset

https://docs.djangoproject.com/en/3.0/topics/forms/formsets/

basemodelformset

https://docs.djangoproject.com/en/3.0/topics/forms/modelforms/#django.forms.models.BaseModelFormSet

django에 mysql을 사용하려고 했으나 연결이 어려웠다. 아래 방법들을 다 시도해보고 결국 export PATH=$PATH:/usr/local/mysql/bin/ 를 통해 mysql config위치를 다시 정해 주고 pymysql을 설치하고 django downgrade로 문제 해결 했다.

pymysql 설치 https://stackoverflow.com/a/56312295

django downgrade https://stackoverflow.com/a/58448370

최종적으로 사용된 modules

image
image

.

.

.

image

.

.

.

image

.

.

.

image

위와 같은 error 발생

.

.

ModuleNotFoundError: No module named ‘MySQLdb’

https://stackoverflow.com/questions/53024891/modulenotfounderror-no-module-named-mysqldb

unable to install mysqlclient in python 3.7.3
https://stackoverflow.com/questions/57808191/unable-to-install-mysqlclient-in-python-3-7-3

What’s the difference between MySQLdb, mysqlclient and MySQL connector/Python?

https://stackoverflow.com/questions/43102442/whats-the-difference-between-mysqldb-mysqlclient-and-mysql-connector-python/46396881#46396881

pip install mysql-python fails with EnvironmentError: mysql_config not found
https://stackoverflow.com/questions/5178292/pip-install-mysql-python-fails-with-environmenterror-mysql-config-not-found

Import error :No module named MYSQLdb

https://askubuntu.com/questions/583415/import-error-no-module-named-mysqldb/1048285#1048285

Django – installing mysqlclient error: mysqlclient 1.3.13 or newer is required; you have 0.9.3

https://stackoverflow.com/questions/55657752/django-installing-mysqlclient-error-mysqlclient-1-3-13-or-newer-is-required