views:

498

answers:

1

I have two models in my Django application, for the purposes of storing search parameters used for some homology search programs:

# models.py
class Search(models.Model):
    """A class to represent search runs."""

    program = models.CharField(max_length=20)
    results_file = models.FileField(
        upload_to=(SEARCH_RESULTS_DIR)
    )
    timestamp = models.DateTimeField()

    def __unicode__(self):
        return u'%s %s' % (self.program, self.timestamp)


class FastaRun(models.Model):

    search = models.OneToOneField('Search', primary_key=True)
    # the user-input FASTA formatted protein sequence
    query_seq = models.TextField()
    # -b "Number of sequence scores to be shown on output."
    number_sequences = models.PositiveIntegerField(blank=True)
    # -E "Limit the number of scores and alignments shown based on the
    # expected number of scores." Overrides the expectation value.
    highest_e_value = models.FloatField(default=10.0,
            blank=True)
    # -F "Limit the number of scores and alignments shown based on the
    # expected number of scores." Sets the highest E-value shown.
    lowest_e_value = models.FloatField(blank=True)
    mfoptions = [
            ('P250', 'PAM250'),
            ('P120', 'PAM120'),
            ('BL50', 'BLOSUM50'),
            ('BL62', 'BLOSUM62'),
            ('BL80', 'BLOSUM80')
    ]
    matrix_file = models.CharField(
            max_length=4,
            choices=mfoptions,
            default='BL50'
    )
    database_option = models.CharField(
            max_length=25,
            choices=BLAST_DBS,
            default=INITIAL_DB_CHOICE
    )
    ktupoptions = [(1, 1), (2, 2)]
    ktup = models.PositiveIntegerField(
            choices=ktupoptions,
            default=2,
            blank=True
    )

Note here that FastaRun is a kind of Search. FastaRun extends search in that there are more parameters defined for a FastaRun. A FastaRun must have a Search instance to which it is linked, and this Search instance is the primary key of FastaRun.

I have a ModelForm for the FastaRun class.

# views.py
class FastaForm(forms.ModelForm):

    class Meta:
        model = models.FastaRun

I have a view function which I need to use to populate a FastaForm and save a new Search instance and a new FastaRun instance based on the user submitted form. The form does not include the option to choose a Search instance. This is impossible to do because the Search instance can only exist once the user actually submits this search.

Below is an outline of what the function needs to do:

# also in views.py
def fasta(request, ...):
    # populate a FastaForm from the information POSTed by the user--but
    # how to do this when there's no Search information coming in from
    # the user's request. We need to create that Search instance, too,
    # but we also have to...

    # validate the FastaForm
    # ... before we can ...

    # create a Search instance and save() it

    # use this saved Search instance and give it to the FastaForm [how?]

    # save() the FastaForm [save the world]

    pass

Because the Search and FastaRun (and therefore FastaForm) are intertwined, I feel like I'm getting into a Catch-22. I need to save a Search instance, whose parameters are stored in the POST request, but whose parameters must be validated using the FastaForm's validation. However, I think the FastaForm can't be instantiated until I have instantiated a Search instance. Yet, I can't instantiate a Search instance until I've validate using the FastaForm... You get the idea.

What am I missing here? There must be a fairly clean way to do this, but I lack the clarity to see it.

Also, correct me if I'm wrong, but this same dependency situation could occur any time you have some sort of relationship between models (e.g., also for ForeignKey and ManyToMany fields). Thus, someone's surely figured this out.

+1  A: 

This is a case where I would use inheritance to take care of this:

# models.py
class Search(models.Model):
    """A class to represent search runs."""
    ...

class FastaRun(Search):
    # one-to-one field has been removed
    ....

Now, instantiating a FastaRun is by definition also instantiating a Search. Django handles the database side of this properly, too, by setting up a separate table for FastaRun with a key into Search. Your validation should work as expected with the form. The only thing you might want to add if you're going to do any queries on the Search object would be to add a type field to Search that is the overridden by all subclasses, so you can filter out those results.

tghw
program is a CharField that identifies the program that was run to do the search (e.g., 'fasta35' or 'ssearch35' or 'blast'). The 'fasta35' and 'ssearch35' must be saved as FastaRun instances. This should suffice as a filter, correct? Is there a way to go from a Search instance to its corresponding FastaRun instance (we may assume it has one)?
gotgenes
Yeah, seems that would be a good enough filter, though it seems you'd only want to use it to exclude subclassed items. (For those, you'd want to use the subclass, e.g. FastaRun.objects.filter(...).)If you do need to go from a Search object s to FastaRun (or any other subclass), you can always just do FastaRun.objects.get(id=s.id).
tghw
Great. Thanks for the helpful answer!
gotgenes