简体   繁体   中英

Trying to set non-model field in model form to some other field that exists in the model

I have a form that I need to add an extra field to - and it needs to have an initial value of the data model's time field (called seg_length_time). I didn't expect the code to work because I know I need to convert the time into a string from the model, but I can't seem to figure out how to set the field to begin with. The current code returns an error 'SegmentForm' object has no attribute 'pk'. I have tried id, and a couple of other things but haven't correctly referenced the specific object. For instance - I tried Segemt.object.values('seg_length_time') but then I get all of the seg_length_time key pairs.

See forms.py code below:

import re
from datetime import datetime,timedelta
    
class SegmentForm(forms.ModelForm):
    
  str_seg_length_time = forms.CharField(max_length=8)
    
  class Meta:
    model = Segment      
    exclude = ('seg_length_time_delta',
        #'seg_length_time',
        'seg_run_time',  
        'seg_run_time_delta'   ,
        'seg_remaining_time',
        'seg_remaining_time_delta',
        'program_id',    
        'audit_user',    
    )
    
  def __init__(self, *args, **kwargs):
    obj = Segment.objects.get(id=self.instance.pk)
    self.fields['str_seg_length_time'] = obj.seg_length_time
    super().__init__(*args, **kwargs)
    
  def clean_str_seg_length_time(self):
    str_seg_time = self.cleaned_data['str_seg_length_time']
      
    if  (re.search("[^\d:]", str_seg_time) ):    # check that only digits and : are in our time string
        raise ValidationError("Segment Time: There are illegal characters in segment time. Valid format:[HH:MM:SS].")
    if  (re.search(".*:.*:.*:.*", str_seg_time) ):   # check for more than two : signs
        raise ValidationError("Segment Time: There are too many colons. Valid format:[HH:MM:SS].")
    if ( re.search("^\d+$", str_seg_time) ):       # time defaults to minutes if not specified
        str_seg_time = "00:" + str_seg_time + ":00"
    if ( re.search("^\d*:\d*$", str_seg_time) ):   # time defaults to minutes/seconds if not specified
        str_seg_time = "00:" + str_seg_time 
    try:
      t = datetime.strptime(str_seg_time,"%H:%M:%S")
    except:
      raise ValidationError('Segment Time : Could not convert time to HH:MM:SS - Invalid Time')
    
    self.seg_length_time = t
    return str_seg_time
        
  def form_valid(self, form):
    form.instance.audit_user = self.request.user
    return super().form_valid(form)

The model.py is below:


from django.db import models
from django.urls import reverse
from datetime import datetime, timedelta

class Program(models.Model):
    air_date             = models.DateField(default="0000-00-00")
    air_time             = models.TimeField(default="00:00:00")
    service              = models.CharField(max_length=10)
    block_time           = models.TimeField(default="00:00:00")
    block_time_delta     = models.DurationField(default=timedelta)
    running_time         = models.TimeField(default="00:00:00",blank=True)
    running_time_delta   = models.DurationField(default=timedelta)
    remaining_time       = models.TimeField(default="00:00:00",blank=True)
    remaining_time_delta = models.DurationField(default=timedelta)
    title                = models.CharField(max_length=190)
    locked_flag          = models.BooleanField(default=False)
    deleted_flag         = models.BooleanField(default=False)
    library              = models.CharField(null=True,max_length=190,blank=True)
    mc                   = models.CharField(null=True,max_length=64)
    producer             = models.CharField(null=True,max_length=64)
    editor               = models.CharField(null=True,max_length=64)
    remarks              = models.TextField(null=True,blank=True)
    audit_time           = models.DateTimeField(auto_now=True)
    audit_user           = models.CharField(null=True,max_length=32)

    def convert_to_delta(self,time_in):
      #print("Lengthof time_in = %s" % len(time_in))
      hold_time = time_in.strftime("%H:%M:%S")
      t = datetime.strptime(hold_time,"%H:%M:%S")
      return(timedelta(hours=t.hour, minutes=t.minute, seconds=t.second))

    def calculate_time(self):
      block_time_delta = self.convert_to_delta(self.block_time)
      total_run_time_delta = timedelta(minutes=0)
      for segs in self.segments.all():
        total_run_time_delta += segs.seg_length_time_delta
        segs.seg_run_time_delta = total_run_time_delta
        segs.seg_run_time = f"{segs.seg_run_time_delta}"
        segs.seg_remaining_time_delta = block_time_delta - total_run_time_delta
        segs.seg_remaining_time = f"{abs(segs.seg_remaining_time_delta)}"
        super(Segment,segs).save()

      self.running_time_delta = total_run_time_delta
      self.running_time = f"{self.running_time_delta}"
      self.remaining_time_delta = self.block_time_delta - total_run_time_delta
      self.remaining_time = f"{abs(self.remaining_time_delta)}"

    def calculate_sequence(self):
      for index,segs in enumerate(self.segments.all(),1):
        segs.sequence_number = index
        super(Segment,segs).save()

    def save(self, *args, **kwargs):
      self.calculate_sequence()
      self.calculate_time()
      super().save(*args,**kwargs)

    def __str__(self):
        return f"{self.pk} : {self.title}"

    def get_absolute_url(self):
        #return reverse('program_detail', args=[str(self.id)])
        return reverse('program-detail', kwargs={'pk': self.pk})

class Segment(models.Model):
    class Meta:
      ordering = ['sequence_number']

    program_id = models.ForeignKey(Program,
        on_delete=models.CASCADE,
        related_name='segments',   # link to Program
    )
    sequence_number          = models.DecimalField(decimal_places=2,max_digits=6,default="0.00")
    title                    = models.CharField(max_length=190)
    bridge_flag              = models.BooleanField(default=False)
    seg_length_time          = models.TimeField(null=True,default=None, blank=True)
    seg_length_time_delta    = models.DurationField(default=timedelta)
    seg_run_time             = models.TimeField(default="00:00:00",blank=True)
    seg_run_time_delta       = models.DurationField(default=timedelta)
    seg_remaining_time       = models.TimeField(default="00:00:00",blank=True)
    seg_remaining_time_delta = models.DurationField(default=timedelta)
    author                   = models.CharField(max_length=64,null=True,default=None,blank=True)
    voice                    = models.CharField(max_length=64,null=True,default=None,blank=True)
    library                  = models.CharField(max_length=190,null=True,default=None,blank=True)
    summary                  = models.TextField()
    audit_time               = models.DateTimeField(auto_now=True)
    audit_user               = models.CharField(null=True,max_length=32)

    def save(self, *args, **kwargs):
      self.seg_length_time_delta =   
           self.program_id.convert_to_delta(self.seg_length_time)
      super().save(*args,**kwargs)
      self.program_id.save()

    def get_absolute_url(self):
      return reverse('program_detail', kwargs={'pk': self.program_id.pk} )

    def save( self, *args, **kwargs):
      super().save(*args,**kwargs)
      self.program_id.save()

    def __str__(self):
      return f"{self.title}"

The View is the following:

class ProgramMixView(LoginRequiredMixin,UpdateView):
  model = Segment
  template_name = 'program_mix_view.html'
  form_class = SegmentForm
  context_object_name = 'program_edit'

  def get_context_data(self, **kwargs):
    context = super(ProgramMixView,self).get_context_data(**kwargs)
    cur_segment = Segment.objects.get(id=self.kwargs['pk'])
    context['program_info'] = Program.objects.get(id=cur_segment.program_id.pk)
    return context

class SegmentCreateView(LoginRequiredMixin,CreateView):
  model = Program
  template_name = 'segment_create_view.html'
  form_class = SegmentForm
  context_object_name = 'program_seg_create'

And the actual template program_mix_template.html

  {% if program_info %}
    <TABLE ID="pro-table" WIDTH="100%">
    <!--  <TABLE BORDER="0" TABLE_LAYOUT="fixed" WIDTH="100%"> -->
      <TR BGCOLOR="#15C1B5">
        <TD ALIGN="Right">Program Title:</TD><TD ALIGN="Left">{{ program_info.title|truncatechars:30 }}</TD>
        <TD ALIGN="Right">Library:</TD><TD ALIGN="Left">&nbsp;{{ program_info.library }}</TD>
        <TD ALIGN="Right">Service Bureau:</TD><TD ALIGN="Left">&nbsp;{{ program_info.service }}</TD>
      </TR>
      <TR BGCOLOR="#15C1B5">
        <TD ALIGN="Right">Program Id:</TD><TD ALIGN="Left">&nbsp;{{ program_info.pk }}</TD>
        <TD ALIGN="Right">Air Date:</TD><TD ALIGN="Left">&nbsp;{{ program_info.air_date }}</TD>
        <TD ALIGN="Right">Air Time</TD><TD ALIGN="Left">&nbsp;{{ program_info.air_time|time:"H:i:s" }}</TD>
      </TR>
      <TR BGCOLOR="#15C1B5">
        <TD ALIGN="Right">Producer:</TD><TD ALIGN="Left">&nbsp;{{ program_info.producer }}</TD>
        <TD ALIGN="Right">Editor:</TD><TD ALIGN="Left">&nbsp;{{ program_info.editor }}</TD>
        <TD ALIGN="Right">MC:</TD><TD ALIGN="Left">&nbsp;{{ program_info.mc }}</TD>
      </TR>
      <TR BGCOLOR="#15C1B5">
        <TD BGCOLOR="#99CCFF" ALIGN="Right">Duration:</TD>
            <TD BGCOLOR="#99CCFF" ALIGN="Left">&nbsp;{{ program_info.block_time|time:"H:i:s" }}</TD>
        <TD BGCOLOR="#8DF1BF" ALIGN="Right">Rem. Time:</TD>
            <TD BGCOLOR="#8DF1BF" ALIGN="Left">&nbsp;{{ program_info.remaining_time|time:"H:i:s" }}</TD>
        <TD BGCOLOR="#CC99CC" ALIGN="Right">Run Time:</TD>
            <TD BGCOLOR="#CC99CC" ALIGN="Left">&nbsp;{{ program_info.running_time|time:"H:i:s" }}</TD>
      </TR>
      <TR BGCOLOR="#15C1B5">
        <TD ALIGN="Right">Remarks:</TD><TD ALIGN="Left" COLSPAN="5"><PRE>{{ program_info.remarks|truncatechars:180 }}</TD>
        </PRE></TD>
      </TR>
      </TABLE>
      {% if program_info.segments.all %}
        <TABLE BORDER="0" TABLE_LAYOUT="fixed" WIDTH="100%">
          <TR BGCOLOR="#B0B0FF">
            <TD ALIGN="Center">&nbsp;#</TD>
            <TD ALIGN="Center">Segment Title</TD>
            <TD ALIGN="Center">Summary</TD>
            <TD ALIGN="Center">Library</TD>
            <TD ALIGN="Center">Author</TD>
            <TD ALIGN="Center">Voice</TD>
            <TD ALIGN="Center">Segment time</TD>
            <TD BGCOLOR="#CC99CC" ALIGN="Center">Run time</TD>
            <TD BGCOLOR="#8DF1BF" ALIGN="Center">Rem. time</TD>
          </TR>
          {% for segments in program_info.segments.all %}
            {% if  program_edit.pk  == segments.id %}
              <form method="post" action="">
                {% csrf_token %}
                <TR BGCOLOR="#B0B0FF">
                  <TD ALIGN="Left" VALIGN="Top" WIDTH="35">{{ form.sequence_number }}</TD>
                  <TD ALIGN="Left" VALIGN="Top">{{ form.title }}</TD>
                  <TD ALIGN="Left" VALIGN="Top"><PRE>{{ form.summary }}</PRE></TD>
                  <TD ALIGN="Left" VALIGN="Top">{{ form.library }}</TD>
                  <TD ALIGN="Left" VALIGN="Top">{{ form.author }}</TD>
                  <TD ALIGN="Left" VALIGN="Top">{{ form.voice }}</TD>
                  <TD ALIGN="CENTER" VALIGN="Top" WIDTH="10">{{ form.str_seg_length_time }}</TD>
                </TR>
                <TR>
                  <td><input type="submit" Value="Update"></td>
                </tr>
              </form>
              {% if form.errors %}
                <!-- Error messaging -->
                <div id="errors">
                    <div class="inner">
                        <p>There were some errors in the information you entered. Please correct the following:</p>
                        <ul>
                            {% for field in form %}
                                {% if field.errors %}
                                    <li> {{ field.errors|striptags }}</li><br>
                                {% endif %}
                            {% endfor %}
                        </ul>
                    </div>
                </div>
              {% endif %}
            {% else %}
              <tr BGCOLOR="#B0B0FF">
                <TD ALIGN="Left" VALIGN="Top" WIDTH="35">{{ segments.sequence_number }}</TD>
                <TD ALIGN="Left" VALIGN="Top">{{ segments.title }}</TD>
                <TD ALIGN="Left" VALIGN="Top"><PRE>{{ segments.summary|truncatechars:40 }}</PRE></TD>
                <TD ALIGN="Left" VALIGN="Top">{{ segments.library }}</TD>
                <TD ALIGN="Left" VALIGN="Top">{{ segments.author }}</TD>
                <TD ALIGN="Left" VALIGN="Top">{{ segments.voice }}</TD>
                <TD ALIGN="CENTER" VALIGN="Top" WIDTH="10">{{ segments.seg_length_time|time:"H:i:s" }}</TD>
                <TD BGCOLOR="#CC99CC" ALIGN="CENTER" VALIGN="Top" WIDTH="10">{{ segments.seg_run_time|time:"H:i:s" }}</TD>
                <TD BGCOLOR="#8DF1BF" ALIGN="CENTER" VALIGN="Top" WIDTH="10">{{ segments.seg_remaining_time|time:"H:i:s" }}</TD>
              </TR>
            {% endif %}
          {% endfor %}
        </TABLE>
      {% else %}
        No Segments
      {% endif %}
    {% endif %}

The field (str_seg_length_time)shows up on the form as nothing (Blanks), if I don't do some sort of initial code to set it from the model. Can I get the object by id, or dont I already have the object in the form at initialization time? I assume I will need to do some sort of strfdatetime() call to turn it into a string, but I don't know how to get the seg_length_time from the model to begin with...

Thanks. (PS I don't just put the field from the model in the form because I am trying to change the way the default time entered is handled (HH:MM is the default - I need mm:ss or Minutes by default) ie 05:00 should be 5 minutes, NOT 5 hours)...

Your forms __init__ method is written as:

def __init__(self, *args, **kwargs):
    obj = Segment.objects.get(id=self.instance.pk)
    self.fields['str_seg_length_time'] = obj.seg_length_time
    super().__init__(*args, **kwargs)

Let's note the problems here. Firstly obj = Segment.objects.get(id=self.instance.pk) , I can see that you use this same form in a CreateView , obviously when an instance is not yet created it will not have a primary key, giving you an error. Secondly self.fields['str_seg_length_time'] this will give you an error because you are calling this before even calling super().__init__ which is what initializes self.fields . Thirdly = obj.seg_length_time what even are you trying to do here? Do you want to set the initial value of the field? Because currently you replace the field you declare with this value instead.

What is the solution? It appears you just want to provide an initial value for the field, you don't need to override __init__ for this simple purpose. Going even further you just want the input in a custom format? No need to do all this for that, you can simply pass the input_formats [Django docs] kwarg for that to the field. The formats in this list will be tried in order and the first matching one will be used:

class SegmentForm(forms.ModelForm):
    
  seg_length_time = forms.TimeField(input_formats=['%H:%M:%S', '%M:%S', '%M']) # Set custom formats here
    
  class Meta:
    model = Segment      
    exclude = ('seg_length_time_delta',
        # 'seg_length_time',
        'seg_run_time',  
        'seg_run_time_delta'   ,
        'seg_remaining_time',
        'seg_remaining_time_delta',
        'program_id',    
        'audit_user',    
    )

What if you want to use some custom formats for all time fields by default? Simply set the TIME_INPUT_FORMATS setting [Django docs] in settings.py :

TIME_INPUT_FORMATS = [
    '%H:%M:%S',
    '%M:%S',
    '%M'
]

In your initialization of the SegmentForm the keyword self will refer to the SegmentForm class, not the model instance. That's why you get the error that the form does not have pk. To refer to the model instance use self.instance , which will refer to the instance of the underlying model.

  def __init__(self, *args, **kwargs):
    obj = Segment.objects.get(id=self.instance.pk)
    self.fields['str_seg_length_time'] = obj.seg_length_time
    super().__init__(*args, **kwargs)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM