저는 장고를 시작한지 얼마 되지 않았고, 장고를 사용하며 의문이 많이 있습니다. 그 첫번째로 도대체 장고 폼은 왜 사용해야 하는것인가.


* 사용하려는 폼의 성격이 얼마나 모델과 관련이 있고, 스타일 등의 커스터마이징을 원하나 생각해보는 것이 폼 구현 방법에 결정에 큰 도움을 준다고 한다. 


일반폼(Form)

=> 모델에서 정의한 필드 외의 값을 다루어야 하는 경우 일반 폼을 사용해야 합니다. 


모델폼 

form.ModelForm을 상속받아 사용합니다. 

모델에 정의한 필드만 html로 렌더링 한다면 이 방법을 사용하는게 좋습니다.


팩토리 함수 !! 

장고 에서는 이런 모델 폼을 더 쉽세 생성할 수 있도록 팩토리 함수를 제공합니다. 

위젯을 통해 상세 설정을 해줄수도 있습니다. 


폼의 끝판왕 제네릭뷰

모델과 관련된 폼을 뷰를 통해 렌더링 하는 경우가 많더라는 거죠 제네릭을 이용한 폼은 이러한 경우에 최고의 방법입니다. 



아니 이런거 다 알겠는데... 왜 굳이 폼을 쓰지 ?

그냥 모델에서 다이렉트로 가져가면 문제가 생기나 ?

아직도 의문이넹...



현재 이력서 앱을 만들고 있습니다.

근데 막히는 부분이 많네요...


이번에는 1:N 관계로 설정된 모델 클래스들을 뷰로 어떻게 가져와서 입력을 시킬 것인가에 대한 문제입니다....ㅠㅜ


현재 1:N 관계로 모델에는 다음과 같이 설정이 되어 있습니다. 

인적 사항 (Resume) 1 ------ N 학력, 경력, 병역, 직무, 기술 필드로 각각 설정되어 있습니다. 



forms.py

class ResumeForm(forms.ModelForm):
    class Meta:
        model = Resume
        fields = ['user', 'occupation', 'current_salary', 'hope_salary']


class EducationForm(forms.ModelForm):
    class Meta:
        model = Education
        fields = ['colleage_name', 'is_overseas', 'major', 'start_date', 'completion_date', 'grade']


class CareerForm(forms.ModelForm):
    class Meta:
        model = JobCareer
        fields = ['company', 'partname', 'position', 'duty',
                  'start_date', 'is_current', 'completion_date',
                  'job', 'description']


class LicenceForm(forms.ModelForm):
    class Meta:
        model = License
        fields = ['name', 'rank', 'publishing', 'get_date']


class TrainingForm(forms.ModelForm):
    class Meta:
        model = Training
        fields = ['title', 'place']

 이런식으로 모든 외래키 참조 모델필드를 가져와서 뷰에서 이런 형식으로 넘기고 있는데 이게 맞는건지는 잘 모르겠네요... 


views.py

def resume(request):
    user_form = ResumeForm(request.POST or None)
    edu_form = EducationForm(request.POST or None)
    career_form = CareerForm(request.POST or None)
    license_form = LicenceForm(request.POST or None)
    military_form = MilitaryForm(request.POST or None)
    training_form = TrainingForm(request.POST or None)

    if user_form.is_valid():

        user = user_form.save(commit=False)
        edu = edu_form.save(commit=False)
        career = career_form.save(commit=False)
        license = license_form.save(commit=False)
        military = military_form.save(commit=False)
        training = training_form.save(commit=False)

        user.save()

        edu.user = user
        edu.save()

        career.user = user
        career.save()

        license.user = user
        license.save()

        military.user = user
        military.save()

        training.user = user
        training.save()

        return HttpResponseRedirect('/accounts/resume_list.html')

    template = 'accounts/resume_form.html'

    context = {
        'form': user_form,
        'edu_form': edu_form,
        'career': career_form,
        'license': license_form,
        'military': military_form,
        'training': training_form

    }
    return render(request, template, context)

많은 곳을 뒤져 봐도 외래키 필드에도 입력을 받을 수 있게 하는 곳은 찾기가 어려운 듯합니다. 

좋은 의견 주시면 감사하겠습니다. 

문제 



모델에 MyUser, ActivationProfile, Profile 이렇게 3개 테이블이 있는데 모두 MyUser 모델과 관계를 맺고 있습니다. 

MyUserModel에 User정보가 등록이 되면 그에 따라서 ActivationProfile과, Profile 오프젝트가 생성이 되도록 하는데 왜 외래키를 참조하고 있는 모델들이 오브젝트 생성이 안되는 현상입니다. 


해결 


외래키로 연결을 하고 있는 Pofile 에 email, phone에 'unique=True' 라는 옵션을 제거합니다. 

옵션하나로 ... 문제가 쉽게 해결이 되네요.  


Model.py 

class MyUserManager(BaseUserManager): def create_user(self, username, phone, email, password=None): """ Creates and saves a User with the given email, date of birth and password. """ if not email: raise ValueError('Users must have an email address') user = self.model( username = username, phone = phone, email=self.normalize_email(email), ) user.set_password(password) user.save(using=self._db) return user def create_superuser(self, username, phone, email, password): """ Creates and saves a superuser with the given email, date of birth and password. """ user = self.create_user( username, phone, email, password=password, ) user.is_admin = True user.is_staff = True user.save(using=self._db) return user class MyUser(AbstractBaseUser): username = models.CharField(max_length=100, unique=True) email = models.EmailField( verbose_name='email address', max_length=255, unique=True, ) phone = models.CharField(max_length=15, unique=True) is_active = models.BooleanField(default=True) is_staff = models.BooleanField(default=False) is_admin = models.BooleanField(default=False) objects = MyUserManager() USERNAME_FIELD = 'username' REQUIRED_FIELDS = ['phone','email'] def __str__(self): return self.username def has_perm(self, perm, obj=None): "Does the user have a specific permission?" # Simplest possible answer: Yes, always return True def has_module_perms(self, app_label): "Does the user have permissions to view the app `app_label`?" # Simplest possible answer: Yes, always return True class ActivationProfile(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) key = models.CharField(max_length=120) expired = models.BooleanField(default=False) def save(self, *args, **kwargs): self.key = code_generator() super(ActivationProfile, self).save(*args, **kwargs) def post_save_activation_receiver(sender, instance, created, *args, **kwargs): if created: #send Email print('id activation created') url = "/activate" + instance.key post_save.connect(post_save_activation_receiver, sender=ActivationProfile) class Profile(models.Model): user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True,) phone = models.CharField(max_length=15, unique=True) # 오류 발생 unique key 삭제 email = models.EmailField(max_length=255, unique=True) # 오류 발생 unique key 삭제 def __str__(self): return str(self.user.username) def post_save_user_model_receiver(sender, instance, created, *args, **kwargs): if created: try: #이곳이 문제 Profile.objects.create(user=instance) ActivationProfile.objects.create(user=instance) except: print(settings.AUTH_USER_MODEL) post_save.connect(post_save_user_model_receiver, sender=settings.AUTH_USER_MODEL)


하하... 이 오류 찾는데 하루... 

위 포스팅은 다음 링크의 글을 번역한 것입니다. 

How to Create Django Signals





Django Signals은 특정 이벤트가 발생할 때 응용 프로그램에 알릴 수있는 전략입니다. 특정 모델 인스턴스가 업데이트 될 때마다 캐시 페이지를 무효화하려는 경우 코드 기반에서, 이 모델을 업데이트 할 수있는 위치가 여러곳 있다고 가정해 보겠습니다. 이 특정 모델의 save메소드가 실행이 될 때마다 실행될 코드 일부를 연결하여 Signals를 사용하여 이를 수행 할 수 있습니다.


 또 다른 일반적인 사용 사례는 일대일 관계를 통해 프로필 전략을 사용하여 사용자 Custom Django 사용자를 확장 한 경우입니다. 우리가 일반적으로하는 일은 "Signal dispatcher"를 사용하여 사용자의 post_save 이벤트를 수신하여 프로필 인스턴스도 업데이트하는 것입니다. 다른 게시물에서이 사례를 다뤘습니다. 여기에서 읽을 수 있습니다 : 장고 사용자 모델을 확장하는 방법.


이 튜토리얼에서는 기본 제공 신호를 소개하고 모범 사례에 대한 일반적인 조언을 제공합니다.




언제 사용해야 하는가 ?


우리가 더 나아가기 전에, 우리가 언제 사용하는지 알아야 합니다. 


- 많은 코드의 조각들이 같은 이벤트에 연결되어 있을때 (interested 를 연결로 의역) 

- 분리된 어플리케이션들의 상호작용이 필요할때

      - A Django Core model

- A model Defined by a third-part app 





어떻게 작동하는가 ?


Observer Design Pattern에 익숙하다면, 장고가 그것을 어떻게 구현하는지 알 수 있습니다. 동일한 목적으로 사용됩니다.


Signals 시스템에는 '송신자(senders)'와 '수신자(receivers)'라는 두 가지 핵심 요소가 있습니다. 이름에서 알 수 있듯이 Senders는 신호를 전달하는 책임자이고 Receivers 는이 신호를 수신 한 다음 무언가를 수행하는 객체입니다.


- Receivers는 신호를 수신하는 함수 또는 인스턴스 메소드이여야 합니다.

- Senders는 Sender로 부터 이벤트를 받으려면 Python 개체이거나 None이어야합니다.


Sender와 Receiver 사이의 연결은 connect 메소드를 통해 Signal의 인스턴스 인 "signal dispatcher"를 통해 수행됩니다.


Django Core는 또한 ModelSignal을 정의합니다.이 ModelSignal은 Sender가 app_label.ModelName 형식의 문자열로 지연 지정되도록 허용하는 Signal의 하위 클래스입니다. 하지만 일반적으로 Signal 클래스를 사용하여 사용자 정의 신호를 생성하려고합니다.


따라서 신호를 수신하려면 Signal.connect () 메서드를 사용하여 신호를 보낼 때 호출되는 수신기 함수를 등록해야합니다.





사용법


post_save 의 built-in Signal 에 대해 살펴 보겠습니다. 그 코드는 django.db.models.signals 모듈에 있습니다. 이 특별한 Signal은 save 메소드를 실행한 모델이 끝난 직후에 발생합니다.

from django.contrib.auth.models import User
from django.db.models.signals import post_save

def save_profile(sender, instance, **kwargs):
    instance.profile.save()

post_save.connect(save_profile, sender=User)

위 예제에서 save_profile은 리시버 함수이고, User는 보낸 사람이고 post_save는 신호입니다. 사용자 인스턴스가 save 메소드의 실행을 완료 할 때마다 save_profile 함수가 실행됩니다.


post_save.connect (save_profile)와 같이 송신자 인자를 입력하지 않는다면, save_profile 함수는 Django 모델이 save 메소드를 실행한 후에 실행될 것입니다.


신호를 등록하는 또 다른 방법은 @receiver 데코레이터를 사용하는 것입니다.

def receiver(signal, **kwargs)

signal 매개 변수는 Signal 인스턴스 또는 Signal 인스턴스의 List/Tuple 일 수 있습니다.


아래 예를 참조하십시오.

from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=User)
def save_profile(sender, instance, **kwargs):
    instance.profile.save()


수신기 기능을 여러 신호에 등록하려면 다음과 같이하십시오.

@receiver([post_save, post_delete], sender=User)



코드는 어디에 있어야합니까?


응용 프로그램의 signal를 등록하는 위치에 따라 코드를 가져 오기 때문에 몇 가지 부작용이 발생할 수 있습니다. 따라서 모델 모듈 또는 응용 프로그램 루트 모듈에 두는 것을 피하는 것이 좋습니다.


Django 문서는 앱 구성 파일에 신호를 넣도록 조언합니다. profile이라는 Django 앱을 고려할 때 평소에하는 작업입니다


profiles/signals.py :


from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver

from cmdbox.profiles.models import Profile

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)

@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    instance.profile.save()

profiles/app.py :

from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _

class ProfilesConfig(AppConfig):
    name = 'cmdbox.profiles'
    verbose_name = _('profiles')

    def ready(self):
        import cmdbox.profiles.signals  # noqa

profiles/__init__.py :

default_app_config = 'cmdbox.profiles.apps.ProfilesConfig'

위 예제에서 @receiver () 데코레이터를 사용하기 때문에 ready () 메서드 내에서 signals 모듈을 가져 오는 것만으로 작동합니다. connect () 메서드를 사용하여 리시버 함수를 연결하는 경우 아래 예제를 참조하십시오.

from cmdbox.profiles.models import Profile

def create_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)

def save_user_profile(sender, instance, **kwargs):
    instance.profile.save()

profiles/signals.py:

from django.apps import AppConfig
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.utils.translation import ugettext_lazy as _

from cmdbox.profiles.signals import create_user_profile, save_user_profile

class ProfilesConfig(AppConfig):
    name = 'cmdbox.profiles'
    verbose_name = _('profiles')

    def ready(self):
        post_save.connect(create_user_profile, sender=User)
        post_save.connect(save_user_profile, sender=User)

Note : INSTALLED_APPS 설정에서 이미 AppConfig를 참조하고 있다면 profiles / __ init__.py 세팅은 필요하지 않습니다.

default_app_config = 'cmdbox.profiles.apps.ProfilesConfig'


그 외 내장 Signal 함수를 찾고 싶다면 장고 문서를 참조해 주세요.


저번에는 순환참조가 말썽이더니 이번에는 url관련.. 문제가 저를 괴롭히네요.

일단 에러 메세지는 다음과 같이 나옵니다. 

분명 매칭 url이 없을때 NoreverseMatch에러가 나오는 것으로 알고 있는데... 설정은 잘 맞게 한것 같은데 해결이 안됩니다.



에러메세지는 다음과 같습니다.





Models.py
from django.db import models
from django.urls import reverse
from django.db.models.signals import pre_save, post_save
from django.utils.text import slugify

class CompanyModel(models.Model):
    cmp_name = models.CharField(max_length=100)
    cmp_descript = models.TextField()
    cmp_email = models.EmailField()
    slug = models.SlugField(max_length=128)
    updated = models.DateTimeField(auto_now_add=True, auto_now=False)

    def __str__(self):
        return self.cmp_name

    def get_absolute_url(self): # reverse 함수 설정 *************
        return reverse('company_detail', kwargs={"slug":self.slug})
Views.py
from django.shortcuts import render
from .forms import CompanyForm
from .models import CompanyModel
from django.contrib.auth.decorators import login_required
from django.views.generic.base import TemplateView
from django.views.generic.detail import DetailView
from django.views.generic.list import ListView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.urls import reverse


class CmpCreate(CreateView):
    form_class = CompanyForm
    template_name = "create.html"
    fields = ["cmp_name", "cmp_email", c"cmp_descript"]

    def get_success_url(self):
        return reverse("company-list")

class CompanyUpdateView(UpdateView):
    form_class = CompanyForm
    template_name = "create.html"

class CmpDetail(DetailView): #DetailView 설정
    model = CompanyModel

class CmpListView(ListView):
    model = CompanyModel
urls.py
from django.views.generic.base import TemplateView
from .views import DashboardTemplateView, CmpDetail, CmpListView, CmpCreate, CompanyUpdateView
from django.conf.urls import include
from django.urls import path
from . import views

#보기 편하게 하기 위해서 나머지 불필요한 url은 모두 주석처리 했습니다. 
urlpatterns = [
    #path('', views.home, name='home'),
    #path('about/', TemplateView.as_view(template_name="about.html"), name="about"),
    # path('about2/', DashboardTemplateView.as_view(), name='dashboard'),
    #path('company/create/', CmpCreate.as_view(), name="company-create"),
    #path('company/', CmpListView.as_view(), name="company-list"),
    path('company//', CmpDetail.as_view(), name="company_detail"), #디테일 뷰 slug를 찾지 못함. ********
    #path('company//update', CompanyUpdateView.as_view(), name="company_update"),
    #path('login/', TemplateView.as_view(template_name="login.html"), name="login"),
    #path('accounts/', include('registration.backends.default.urls')),
    #path('contact/', views.contact, name="contact")
]

분명 get_absolute_url 을 설정하고 그것을 자연스럽게 urls.py로 넘겨줬는데 왜 ReverseMatch 에러가 나는지 잘 이해를 하지 못하겠습니다... 도대체... 왜 그럴까요..ㅠㅜ




문제해결..


이번 문제도 너무나도 간단하게 풀려버렸습니다. askDjango의 이진석 님께서 조언을 해주셨는데요. 


Slug 필드를 확인을 해보았습니다. 



위 사진과 같이 SLUG 필드가 한개 빠져있더군요.


영문으로 쓰면 그것을 slugify로 변환을해서 저장을 하는데... 한글은 그게 안되는 것이였음.

하하.... 그래서 slug를 가져오지 못해서 에러가 나더군요. 


만약 한글로 저장을 하게 된다면 숫자로라도 Slug필드를 넣어야 겠습니다. 읕...

오늘 하루종일 찾다가 질문을 올립니다. 
Forms.py에 2번째 라인 Model 을 import 하는 부분에서 계속 적으로 에러가 발생합니다. 
잘 작동되다가 어느순간부터 importerror가 나는데, 쉽게 잡힐줄 알았던 에러가... 답이 안보이네요.
* 파일들은 모두 같은 디렉터리 안에 있습니다. 

Forms.py // 2번째 라인 문제

from django import forms from . models import CompanyModel #CompanyModel이 임포트가 안됨. class CompanyForm(forms.ModelForm): class Meta: model = CompanyModel fields = ["cmp_name", "cmp_email", "cmp_descript", "slug"] def clean_cmp_name(self): cmp_name = self.cleaned_data.get('cmp_name') # write validation code. return cmp_name

Models.py 
from django.db import models
from django.urls import reverse
from django.db.models.signals import pre_save, post_save
from django.utils.text import slugify
from .views import CmpDetail

# Create your models here.

class CompanyModel(models.Model):
    cmp_name = models.CharField(max_length=100)
    cmp_descript = models.TextField()
    cmp_email = models.EmailField()
    slug = models.SlugField()
    updated = models.DateTimeField(auto_now_add=True, auto_now=False)

    class Meta:
        odering = ["-updated"]

    def __str__(self):
        return self.cmp_name

    def get_absolute_url(self):
        return reverse("CmpDetail", args={"slug":self.slug})

View.py

from django.shortcuts import render
from .forms import CompanyForm
from .models import CompanyModel
from django.contrib.auth.decorators import login_required
from django.views.generic.base import TemplateView
from django.views.generic.detail import DetailView
from django.views.generic.list import ListView


class CmpDetail(DetailView):
    model = CompanyModel

class CmpListView(ListView):
    model = CompanyModel

    def get_queryset(self, *args, **kwargs):
        qs = super(CmpListView, self).get_queryset(*args, **kwargs)
        print(qs)
        print(qs.first())
        return qs

ERRORPAGE : Models.py가 임포트가 안된다는것 같은데... 답이 없네요. 

  

 File "", line 994, in _gcd_import
  File "", line 971, in _find_and_load
  File "", line 955, in _find_and_load_unlocked
  File "", line 665, in _load_unlocked
  File "", line 678, in exec_module
  File "", line 219, in _call_with_frames_removed
  File "c:\Projects\keras_talk\My_Django_Stuff\djmod\company\models.py", line 5, in 
    from .views import CmpDetail
  File "c:\Projects\keras_talk\My_Django_Stuff\djmod\company\views.py", line 2, in 
    from .forms import CompanyForm
  File "c:\Projects\keras_talk\My_Django_Stuff\djmod\company\forms.py", line 2, in 
    from . models import CompanyModel
ImportError: cannot import name 'CompanyModel'

좋은 의견 있으면 부탁드립니다. 





AskDjango의 이진석 님께서 답변을 주셨습니다. 

에러는 생각 이상으로 쉽게 잡혔네요... 


Models.py에 


from .views import CmpDetail 부분을 참조 시킨것에서 문제가 발생 했습니다. 

순환참조라고 하는데... 파이썬에서는 기능들을 이것저것 막 가져다 붙이면 문제가 발생하는 군요.. ㅋ 


혹시 ImportError가 발생하였다면 모델.py에 필요없는 부분이 임포트가 되었는지 확인해 보시기 바랍니다. 


from .views import CmpDetail





장고를 나도 처음 사용해 보는 거라 Registration Redux를 적용할때 참... 이해가 가지 않는 몇몇 부분이 있다. 이번에도 register부분에서 에러가 났는데, 해결방법은 이것저것 찾아보니 Setting에 Email 세팅을 안했더라...


Setting.py

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' 를 추가 


Register 를 등록할떄 왜 Email 셋팅이 필요한가 했는데... 가입을 할 때 Email 인증이 필요하기 때문.



이후 관리자 페이지에서 해당 아이디에 대한 인증값을 복사한뒤

 

http://127.0.0.1:8000/accounts/activate/17d2f950ecf031a0d939bfb854703a669eebef97


해당 계정을 활성화 시키면 됨. 

이 과정은 아마도 계정 이메일을 셋팅하고 설정을 하면 되지만, local 단계에서는 이해정도로 빨리 습득을 해야 하기 때문에 실제 이메일 Sent는 하지 않았음.



참... 편리하게 만들어놨네.


ModuleNotFoundError: No module named 'helloeb2.settings'





AWS 나 다른 환경으로 배포하기 위해 settings.py 를 분기시켜야 한다. 


그런데 말입니다. 



1. 기존 settins.py 외 settings 폴더를 생성한다.

2. settings 폴더내 common, dev, prod 파일을 생성한다. 

3. 이후 common 파일에 기존 settings 파일의 내용을 복사하고 settings 파일 삭제

4.  최상단 manage.py 의 settings 경로를 settings/dev 로 수정한다. 



그리고 


ModuleNotFoundError: No module named 'helloeb2.settings'


이 에러가 뜬다. 

분명 다른 사람들은 이렇게 하면 잘만 되던데 왜 나만... ㅅㅂ... 


분명 manage.py에서 수정을 해줬는데도  기존 settings.py을 찾는 이유는 뭘까 ? 

음 ... 몇군데 찾아보니 이유에 대한 원인을 몇가지 찾을 수 있었다. 


1., 분기전 migrations을 하면서 데이터 저장 부분이 settings를 참조하고 있거나

=> 다른 프로젝트를 만들어서 똑같이 해봤지만 에러. 마이그레이션 문제는 아닌듯 하다.


2. 상위 루트 폴더인 helloeb2  부분을 헷갈려서 에러가 나거나

=> 상위 폴더 이름을 변경해봤는데 그래도 오류가 난다. 


3. 장고 버전이 안맞아서 그런다 ?

=> 장고 2.0으로 업데이트 했으나 같은 현상이 일어났다. 





해결책. 



근데 해결은 비교적 간단하게.. . 이렇게 쉽게 풀리다니


os.environ.setdefault("DJANGO_SETTINGS_MODULE", 'helloeb2.settings.dev')

에서


os.environ.setdefault("DJANGO_SETTINGS_MODULE", 'settings.dev')


이렇게 하니 작동을 한다. 


어떤 이유인지는 모르겠으나 상위 디렉토리를 제거하면 된다. 

하... 2시간 날렸네.. 






나의 삽질로 남들의 시간이 단축 될 수 있다면 ... 

글 보고 해결되셨다면 댓글 하나씩만 남겨주세요.