2012-06-07 28 views
2

我正在开发Django中的日历应用程序。处理Django日历应用程序中的重复事件

相关的模型结构如下:

class Lesson(models.Model): 
    RECURRENCE_CHOICES = (
    (0, 'None'), 
    (1, 'Daily'), 
    (7, 'Weekly'), 
    (14, 'Biweekly') 
) 
    frequency = models.IntegerField(choices=RECURRENCE_CHOICES) 
    lessonTime = models.TimeField('Lesson Time') 
    startDate = models.DateField('Start Date') 
    endDate = models.DateField('End Date') 
    student = models.ForeignKey(Student) 

class CancelledLesson(models.Model): 
    lesson = models.ForeignKey(Lesson) 
    student = models.ForeignKey(Student) 
    cancelledLessonDate = models.DateField() # Actual date lesson has been cancelled, this is startDate + Frequency 

class PaidLesson(models.Model): 
    lesson = models.ForeignKey(Lesson) 
    student = models.ForeignKey(Student) 
    actualDate = models.DateField() # Actual date lesson took place 
    paidAmt = models.DecimalField('Amount Paid', max_digits=5, decimal_places=2) 
    paidDate = models.DateField('date paid') 

class CompositeLesson(models.Model): 
    # only used to aggregate lessons for individual lesson management 
    lesson = models.ForeignKey(Lesson) 
    student = models.ForeignKey(Student) 
    actualDate = models.DateTimeField() 
    isCancelled = models.BooleanField() 
    canLesson = models.ForeignKey(CancelledLesson, blank=True, null=True) 
    payLesson = models.ForeignKey(PaidLesson, blank=True, null=True) 

显然,这是显示属于某个学生的经验教训都造成问题。我试图做的是显示一个表格,显示学生的名字加上预定课程的所有实例。我正在动态计算循环以避免炸毁我的数据库。重复例外(即课程取消)存储在他们自己的表格中。在生成重复时,会针对已取消的课程表检查重复次数。

见我的代码生成复发(还有什么问题,这是造成小目录)在这里:Can't get key to display in Django template

我与Python相对缺乏经验,而且现在用这个项目作为一个办法让我的围绕着很多概念,所以如果我错过了本质上是“Pythonic”的东西,我很抱歉。

回答

2

问题的关键部分是您使用少数模型来跟踪一个概念,因此您引入了大量的重复和复杂性。每个附加模型都是“类型”Lesson,所以您应该在这里使用继承。此外,大多数附加模型仅仅跟踪Lesson的特定特征,因此实际上本身不应该是模型。这是我会如何设置它:

class Lesson(models.Model): 
    RECURRENCE_CHOICES = (
     (0, 'None'), 
     (1, 'Daily'), 
     (7, 'Weekly'), 
     (14, 'Biweekly') 
    ) 

    student = models.ForeignKey(Student) 
    frequency = models.IntegerField(choices=RECURRENCE_CHOICES) 
    lessonTime = models.TimeField('Lesson Time') 
    startDate = models.DateField('Start Date') 
    endDate = models.DateField('End Date') 
    cancelledDate = models.DateField('Cancelled Date', blank=True, null=True) 
    paidAmt = models.DecimalField('Amount Paid', max_digits=5, decimal_places=2, blank=True, null=True) 
    paidDate = models.DateField('Date Paid', blank=True, null=True) 

class CancelledLessonManager(models.Manager): 
    def get_query_set(self): 
     return self.filter(cancelledDate__isnull=False) 

class CancelledLesson(Lesson): 
    class Meta: 
     proxy = True 

    objects = CancelledLessonManager() 

class PaidLessonManager(models.Manager): 
    def get_query_set(self): 
     return self.filter(paidDate__isnull=False) 

class PaidLesson(Lesson): 
     class Meta: 
      proxy = True 

     objects = PaidLessonManager() 

你会发现,我的所有属性移动到Lesson。这是它应该的方式。例如,Lesson有一个cancelledDate字段。如果该字段为NULL,则不会被取消。如果它是实际的日期,那么它被取消。不需要另一个模型。

但是,为了指导性目的,我已经将CancelledLessonPaidLesson都留下了。这些现在是Django“代理模型”中所称的。他们没有得到他们自己的数据库表(所以没有讨厌的数据重复)。他们纯粹是为了方便。每个人都有一个自定义管理器来返回相应的匹配Lessons,因此您可以执行CancelledLesson.objects.all()并仅获取那些被取消的Lesson。您还可以使用代理模型在管理员中创建独特的视图。如果您只想为CancelledLesson设置管理区域,则所有数据仍会存入Lesson的一个表格中。

CompositeLesson不见了,好不容易。这是试图将这三个其他模型组合成一个有凝聚力的东西的产物。这不再是必要的,因此您的查询将会更容易显着

编辑

我忘了提及,你可以和应该添加的实用方法的Lesson模型。例如,跟踪取消/不取决于字段是否为NULL或从数据库的角度来看是否有意义,从编程的角度来看,它不是那么直观。其结果是,你可能想要做这样的事情:

@property 
def is_cancelled(self): 
    return self.cancelledDate is not None 

... 

if lesson.is_cancelled: 
    print 'This lesson is cancelled' 

或者:

import datetime 

... 

def cancel(self, date=None, commit=True): 
    self.cancelledDate = date or datetime.date.today() 
    if commit: 
     self.save() 

然后,你可以简单地通过调用lesson.cancel()取消课程,它会默认为今天取消它。如果你想将来取消它,你可以通过一个日期:lesson.cancel(date=tommorrow)(其中tomorrowdatetime)。如果您想在保存之前进行其他处理,请通过commit=False,并且它实际上不会将对象保存到数据库中。然后,当你准备好时,请致电lesson.save()

+0

好的,这是有道理的。我将如何去处理多次取消课程? –

+0

在复发的情况下,通常更好地分离出另一个模型来处理每个日期和时间。就像'Lesson'和'LessonOccurrence'一样。 'LessonOccurrence'将有一个外键返回到它所属的'Lesson',然后是特定于日期的字段,即不是'student'等。 –

0

这就是我想出的。我觉得lessons_in_range()可能不像我所能得到的那样是“Pythonic”,但这是我需要它做的。

class Lesson(models.Model): 
RECURRENCE_CHOICES = (
    (0, 'None'), 
    (1, 'Daily'), 
    (7, 'Weekly'), 
    (14, 'Biweekly') 
) 
relatedLesson = models.ForeignKey('self', null=True, blank=True) 
student = models.ForeignKey(Student) 
frequency = models.IntegerField(choices=RECURRENCE_CHOICES, null=True, blank=True) 
lessonTime = models.TimeField('Lesson Time', null=True, blank=True) 
startDate = models.DateField('Start Date') 
endDate = models.DateField('End Date', null=True, blank=True) 
isCancelled = models.BooleanField(default = False) 
amtBilled = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True) 
amtPaid = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True) 

def get_exceptions(self): 
    return Lesson.objects.filter(relatedLesson = self.id) 

def cancel(self, date=None): 
    if date: 
     x = Lesson() 
     x = self 
     x.pk = None 
     x.relatedLesson = self.id 
     x.isCancelled = True 
     x.startDate = date 
     x.endDate = date 
     x.save() 
    else: 
     self.endDate = datetime.date.today() 
     self.save() 
    return 

def pay_lesson(self, date, amount): 
    x = Lesson() 
    x = self 
    x.pk = None 
    x.relatedLesson = self.id 
    x.amtPaid = amount 
    x.startDate = date 
    x.endDate = date 
    x.save() 
    return 

def lessons_in_range(self, startDate, endDate): 
    if (self.startDate > endDate) or (self.endDate < startDate): 
     return None 
    if self.endDate < endDate: 
     endDate = self.endDate 
    ex = self.get_exceptions() 
    if self.frequency == 0: 
     if ex: 
      return ex 
     else: 
      return self 
    sd = next_date(self.startDate, self.frequency, startDate) 
    lessonList = [] 
    while (sd <= endDate): 
     exf = ex.filter(startDate = sd) 
     if exf: 
      # lesson already exists in database, add it 
      lessonList.append(exf) 
     elif sd == self.startDate: 
      # lesson is the original lesson, add that 
      lessonList.append(self) 
     else: 
      # lesson does not exist, create it in the database then add it to the list 
      x = Lesson() 
      x.student = self.student 
      x.frequency = 0 
      x.lessonTime = self.lessonTime 
      x.relatedLesson = self 
      x.startDate = sd 
      x.endDate = sd 
      x.isCancelled = False 
      x.amtBilled = self.amtBilled  
      x.amtPaid = None 
      x.save() 
      lessonList.append(x) 
     sd += timedelta(self.frequency) 
    return lessonList