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

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


이번에는 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 함수를 찾고 싶다면 장고 문서를 참조해 주세요.


협력 다이어그램이란 ?

객체 사이의 연관관계 뿐만 아니라, 각 객체들이 주고 받는 메시지들을 공간에 따라 배열한 것. 객체 다이어그램의 확장으로 볼 수 있다. 


◎시퀀스 다이어그램과의 유사점 및 차이점

유사점: 시퀀스 다이어그램처럼 객체들 사이의 교류를 보여준다. → 따라서, 시퀀스와 협력다이어그램의 상호 변환이 쉽다.

차이점: 

- 시퀀스 다이어그램: 객체간의 교류를 시간의 순서에 초점을 두어 표현

- 협력 다이어그램 : 공간에 따라 배열시킴, 교류를 수행하는 객체들의 전체적인 조직과 상황(Context)에 초점을 맞춤


◎표현

- 두 객체 사이를 연관선으로 연결. 연관선 위에 메시지가 전달되는 객체 쪽으로 화살표 방향을 가진 선을 긋는다. 메시지의 의미는 "메시지를 받는 객체로 하여금 어떤 오퍼레이션을 실행하라" 라는 의미. 메시지 끝에 '( )' 을 붙임으로써 매개변수를 넣을 수 있도록 한다.


예제) 아래 그림

1. GUI는 키 입력은 운영체제에게 알린다. 

2. 운영체제는 CPU에게 그 사실을 알린다. 

3. 운영체제는 GUI를 갱신한다. 

4. CPU는 비디오 카드에게 GUI갱신에 필요한 명령을 내린다. 

5. 비디오 카드는 모니터로 메시지를 전송한다. 

6. 모니터는 화면에 문자를 표시하고, 사용자에게 피드백을 제공한다.

+ State Diagram 상태변화 추가


표현 

객체의 사각형 안에 객체의 상태를 나타낸다. 변경된 상태의 객체를 나타내는 사각형에다가 쇄선을 그리고 스테레오타입 <<become>>을 붙인다. 


◎"음료수 사기" 쓰임새를 시퀀스 다이어그램으로 표현

1. 소비자가 자판기의 프론트 앞에 서서 투입구에 돈을 넣는다. (insert)

2. 소비자가 마실 음료수를 고른다. (Select)

3. 돈이 금전등록기에 들어간다. (Send)

4. 등록기는 선택된 음료가 디스펜서에 들어있는지를 체크한다. 

5. 간단한 시나리오이기 때무에, 선택된 소다가 준비되어 있고, 등록기는 현금잔고를 갱신한다. 

6. 등록기는 디스펜서를 사용하여 소다를 자동 판매기의 프론트로 보낸다.


◎"음료수 사기" 에서 "액수가 맞지 않는경우"

1. 사용자가 음료수 가격보다 많은 돈을 투입한 경우

2. 음료수 자판기가 충분한 거스름돈을 가진 경우

3. 음료수 자판기가 충분한 거스름돈을 가지지 않은 경우


* 각 단계의 소수점 숫자의 표현은 동일한 단계에서 여러시나리오가 중첩됨을 나타냄

◎ "음료수사기" 시나리오에서 "음료수 자판기가 충분한 거스름 돈을 가지고 있지 않을 때"


1. 거스름 돈이 없음을 알리는 메시지를 출력한다. 

2. 투입된 돈을 돌려준다.

3. 맞는 돈을 넣을 것을 지시한다. 

4. 이때, 사실상 사용자와 자판기와의 거래(Transaction) 는 끝이 난다.

◎While문을 표현 : * [조건]


메시지를 받는 객체 사각형을 사선방향으로 쌓는다.

객체로 전송되는 메시지에는 ' * ' 가 붙은 대괄호 조건문을 붙여준다. 만약, 메시지 전송순서가 필요하다면, 조건문에 1.... n 처럼 순서를 표시할 수있다. 


Ex) 은행원이 여러창구에 늘어선 고객들을 순서대로 맞아 서비스를 하려고 할때.

◎ 동기화

다른 메시지들과 자신의 메시지를 동기화 하는것. 즉, 다른 메시지들이 전송이 이루어진 후에야 자신의 메시지를 전송하는 상황을 의미함.


◎표현 

메시지가 전송되기 전에 완성되어야 할 메시지 리스트를 ' ; '로 구분하여 나열하고 ' / ' 로 리스트의 끝임을 알린다.


◎ 판촉 캠페인 

1. 수석 마케팅 부회장은 판촉 VP에게 특정 제품의 캠페인을 만들것을 요구한다.

2. 판촉 VP는 캠페인을 만들고 이것을 판촉 관리자에게 할당한다.

3. 판촉 관리자는 판매원에게 이 캠페인에 따라 제품을 팔 것을 요구한다.

4. 판매원은 고객들에게 제품을 사줄 것을 부탁한다.

5. 판촉 VP가 할당을 내리고 판촉 관리자가 지시를 마친후에, 회사 홍보 담당 전문가가 지역 신문에 자신들의 캠페인을 광고로 실어줄 것을 요청한다.




◎ 활동 다이어 그램이란?


처리단계(Activity), 결정(Decision), 분기처리(Branch)를 표현하는데 있어서, 업무과정이나 Operation 에서 처리되는 일들을 효과적으로 나타내는데 유용하게 사용된다.


활동 다이어그램은 Flow Chart와 상당히 흡사하다. State Diagram의 확장으로 볼 수 있다. 해당 활동 내의 처리가 끝나면 그 다음의 활동으로 자동적으로 옮겨진다. 

◎ 신호

 

활동이 진행되는 도중에 신호(Signal)을 보낼 수도 있다. 신호가 보내어지면 그 신호를 받은 쪽은 활동을 개시해야 한다.


◎ 동시경로

◎ 짜장면 집에서 주문을 하는 과정 


1. 손님이 메뉴에서 음식을 고른다.

2. 웨이터를 부르고, 주문한다.

3. 웨이터는 주방장에게 주문사항을 알린다.

4. 만약 주문한 음식의 재료가 떨어졌으면 주방장은 웨이터에게 알린다. 

5. 웨이터는 손님에게 알린다.

6. 다시 1번 부터 반복한다.



◎ 활동 다이어그램에 역할 (Role)을 표시함으로써 처리 과정에 속해있는 각 활동의 책임이 누구에게 있는지 나타낼 수 있다. 


저번에는 순환참조가 말썽이더니 이번에는 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필드를 넣어야 겠습니다. 읕...